Installation directions
Before you can run the code in this notebook you should follow the directions from the README.
Overview
In this tutorial we will explore methods for exploration and visualization of large complex datasets using R and Spark. We will cover the following topics:
- Developing skills for exploring data in an iterative fashion. Since it is impossible to predict which views and summaries of a new dataset are the most interesting, an iterative process is required. Therefore, it is important to create and understand multiple views of your data.
- Using the divide and recombine methodology on data to compute summary statistics or prepare for visualization.
- Working with Spark as a scalable back-end for divide and recombine.
- Plotting complex data, especially using the the method of small multiples or conditioning.
This tutorial is mainly about visualization, ranging from summaries to more detailed views of the data. A major component of creating visualizations – large or small – from big data is that there is a lot of data manipulation involved. Consequently, a good deal of the tutorial is spent illustrating how to perform a wide variety of operations on data to get it into shape for visualization.
Introduction to divide and recombine
The divide and recombine or D&R method provides a highly scalable approach to analysis of large complex datasets. With D&R we work with meaningful, persistent divisions of the data. “Big data” is typically big because it is made up of collections of many subsets, sensors, locations, time periods, etc. A schematic view of the D&R process is shown in the figure below.
There are many possible ways to divide data. The best choice depends on the nature of the data and the analysis to be performed. Some possibilities include:
- Break the data up based on data structure and apply visual or analytical methods. We call this conditioning variable division. In practice this approach is common and not new.
- Another option is random replicate division
Once the data are divided, analytic or visual methods are applied independently to each subset in an embarrassingly parallel fashion. The results of these analyses are recombined to yield a statistically valid D&R result or visualization. We refer to these options as:
- Analytic recombination
- Summary or aggregation recombination
- Graphical recombination
In this lesson, our focus is on summary and graphical recombination for the exploration of large complex datasets.
Big Data with R and Spark
This tutorial focuses on the exploration and visualization of large complex datasets using the D&R paradigm. To do so, we need a massively scalable back-end to perform the large scale data operations. In this case we are using a Spark back-end. The architecture our environment is shown schematically in the figure below.
The components of the architecture are:
- Spark back-end performs the large-scale divide and recombine operations.
- Spark can be run locally as we do in this tutorial or on a massive cluster. A Hadoop cluster or some other scalable back end can be used used.
- The D&R operations are performed within a Spark transform pipeline in the Spark session.
- Spark uses highly scalable storage options, such as HDFS.
- sparklyr provides session management and transform orchestration for Spark.
- A local R session running sparklyr and any other required packages controls the environment. sparklyr translates the data munging pipeline defined in R into a transformation pipeline in Spark.
Other D&R Architectures
It is useful to discuss some of the limitations of this architecture with respect to the D&R paradigm and compare it to other D&R software available to understand which architecture is appropriate for different situations and to discuss what we envision as the future of an ideal D&R architecture.
The D&R project originated as an R front-end to Hadoop, called RHIPE, the R and Hadoop Integrated Programming Environment. This R package allows you to write MapReduce code entirely in R and run it against datasets on Hadoop. MapReduce is not always the most straightforward way to think about processing data, so a companion package, “datadr” was created as a front end for specifying D&R tasks that are translated into MapReduce code. A tutorial on this was given last year at Strata Hadoop World and more about these packages can be found at deltarho.org.
Advantages of RHIPE + datadr
- With datadr and RHIPE, you can execute arbitrary R code against arbitrary R objects at scale on a Hadoop cluster - a flexibility that is absolutely critical in almost any real-world analysis, and one that truly leverages the immense library of analytical methods available in R. With dplyr and sparklyr, you are constrained to the set of operations that can be translated into SQL and you are always dealing with tabular data.
- Divisions of data are persistent. It takes a lot of effort to shuffle large amounts of data around, and usually if you have a useful division in mind, it’s something you want to create once and re-use many times. Also, after you’ve created a division, you often want to transform it to something that isn’t tabular anymore. You can do this naturally with datadr and subsequent per-group operations are very natural and fast. With sparklyr, “virtual” divisions are attained using the
group_by verb and must be specified for every operation, and your data is always tabular throughout the process.
Advantages of sparklyr + dplyr
- Based on a very popular, ubiquitous, and expressive interface, dplyr.
- Great development community support (RStudio).
- Momentum.
The Future of D&R Architectures
While there are some critical big data needs that datadr/RHIPE/Hadoop addresses, given the momentum of both Spark and the Tidyverse, which includes dplyr, and the emergence of using list-columns in data frames to handle arbitrary data, we envision a future D&R environment based on these technologies that can give us arbitrary R execution and arbitrary data structures at scale.
In this tutorial, we use sparklyr and are limited to using it to summarize a larger dataset for the purpose of creating visualizations, which gets us pretty far.
Getting Started
Let’s now move on to some hands-on examples.
Starting and Connecting to Spark Cluster
Its time to start a Spark cluster and create a connection with sparklyr. In this case, you will start Spark on your local machine. Spark should be installed on your system already from following the installation instructions. For large scale applications, Spark is run on a remote cluster.
The connection object, called sc in this case, manages the connection between your local R session and Spark. You will use references to the Spark connection whenever you send data and commands to Spark or receive results back.
library(tidyverse)
library(sparklyr)
library(trelliscopejs)
library(forcats)
airlines <- readr::read_csv(file.path('data', 'airlines.csv'))
sc <- spark_connect(master = "local")
Loading Data into Spark
Now that you have a Spark instance running, you can load the data from the .csv file in your local directory into Spark. If you are working with large scale data, you will need to use the more scalable data loading capabilities of Spark and will not load the data from a .csv file.
You do not load your large dataset into your local R session. The point of the D&R paradigm is to use a massively scalable back end for the heavy lifting. Only the recombined results are collected into the local R session. In this case, we are using Spark for our back-end. Other choices, such as Hadoop, would be suitable as well.
Notice, that the first argument of the command below is sc, a reference to the Spark connection you have started. The name assigned, flights_tbl is a reference you will use in R to access the data in Spark. Execute this code to load the data into your Spark session.
flights_tbl <- spark_read_csv(sc, "flights_csv", "data/flights2016.csv.gz")
This may take a few minutes to run. As noted, flights_tbl is a reference to your data in Spark, but we can treat it in many ways like a data frame in R.
To check that the data was read properly, we can print the object. This pulls a subset of the data into our local R session for viewing.
flights_tbl
This gives us a feel for what variables are in the data and how many records there are. Notice also, that we have about 5.6 million rows of data.
A D&R Example: Exploring Data Using dplyr
Now that the data has been loaded into Spark we can start our first divide and recombine (D&R) example. The steps of this D&R example are:
- The data are divided by the airline code using a
group_by operation. In this case, there are 20 groups.
- The mean for each group is computed using the dplyr
summarize verb. These calculations are independent of each other in all respects. They can be done in parallel even on different nodes of a cluster. Any other summary statistics can be computed in parallel as well.
- The results are now just one mean value for each airline. They are easily recombined into a vector and then sorted using the
arrange verb.
Ideally we would have liked to compute quartiles and the median but sparklyr doesn’t support these calculations as part of a dplyr group_by() operation.
The code below, applies a chain of dplyr verbs to the flights_tbl data frame. These operations are performed in Spark and the results transfered to your local R session using the collect verb. Execute this code and examine the result.
cr_arr_delay <- flights_tbl %>%
group_by(carrier) %>%
summarise(
mean_delay = mean(arr_delay),
n = n()) %>%
arrange(mean_delay) %>%
collect()
cr_arr_delay # Print the results
The D&R process has reduced 5.6 million rows of raw data to 12 rows of summary statistics.
For this example, we used the dplyr package with sparklyr. The R dplyr package, combined with sparklyr, is used to script complex data munging and analysis operations in Spark.
- dplyr performs common data manipulation or data munging operations using a series of operators call
verbs.
- Complex data munging operations are constructed by chaining the simple verbs. The output of one verb is connected to the input of the next using the chaining operator,
%>%
- If you are not familiar with dplyr there is a good tutorial vignette on CRAN.
- sparklyr uses a subset of the dplyr verbs to script operation in Spark.
- Verb chains are defined in R.
- The pipeline defined by the verb chain is executed in Spark.
- Results are pulled into the local R session using the
collect verb.
- There are comprehensive tutorials an documentation for sparklyr.
Creating a First Plot
Ultimately in this tutorial we want to demonstrate some powerful visualization tools for interactively exploring large datasets in detail, but with a new dataset, it is often best to first look at some high-level summary visualizations that help guide us toward behaviors we might want to inspect in more detail.
Given the summary statistics output from our operation above, calculating the mean delay by carrier, we will create some plots to further explore the relationships in these results.
As a first step, we need to join some human readable names to the summary statistics data frame.
# merge the airline info so we know who the carriers are
cr_arr_delay <- left_join(cr_arr_delay, airlines)
cr_arr_delay
Now that the dataset is prepared, let’s make some simple plots using the ggplot2 package. The code in cell below uses ggplot to explore the mean delay by airline name and the number of flights by airline.
ggplot(cr_arr_delay, aes(fct_reorder(name, mean_delay), mean_delay)) +
geom_point() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
xlab(NULL) +
ylab("Mean Arrival Delay (minutes)")
Note: In this tutorial we assume you have some exposure to the ggplot2 package.
- The
ggplotfunction defines a data frame to operate on.
- The
aes function defines the columns to use for the various dimensions of the plot, e.g. x, ycolor, shape.
- The plot type attribute is defined by one or more geometry functions, e.g. geom_point, geom_line, geom_boxplot.
- Other plot attributes are defined by the appropriate function, e.g. xlab, ggtitle, theme.
- All of the functions required to create a complete plot are chained together with the chaining operator,
+.
- There is a comprehensive ggplot2 documentation index for all functions available. ***
Your Turn: We have looked at the mean delay of flights by airline. But how does the mean distance of the flight change by airline? Is there a relationship between a carrier’s mean distance and mean delay? In the space below create and execute code to do the following: - Use sparklyr to compute a new cr_arr_delay data frame, including all the same columns as before but also a new mean_distance column. - Join the airline names to the cr_arr_delay data frame. - Use ggplot2 to plot the mean distance by airline.
cat('Your code goes here')
As discussed before, it is important to investigate multiple views of a dataset. Now, the question is, what is the relationship between number of flights and mean delay, and mean delay and mean distance of the flights. The code in the cells below displays these plots.
ggplot(cr_arr_delay, aes(mean_delay, n)) +
geom_point() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
xlab('Mean delay in minutes') +
ylab("Number of flights by airline")
ggplot(cr_arr_delay, aes(mean_delay, mean_distance)) +
geom_point() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
xlab('Mean delay in minutes') +
ylab("Mean distance in miles")
Plotting With a Little More Detail
In the previous example we worked with a fairly simple set of summary statistics. The mean delay, number of flights and mean distance of flights all grouped by a single factor, airline. These relationships give us some interesting insight into these data, but surely, we can learn more about this dataset.
Let’s try another D&R example. In this case we will divide the data both by airline and month. The basic D&R pipeline is similar to the one we used before, but the results are more granular. The code in the cell below performs the following divide and recombine operations:
- The data is divided by each carrier and month pair.
- Summary statistics are computed for each division of the data.
- The recombined results are collected to the local R session.
- The airlines names are joined and the airline codes are substituted for the missing values.
cr_mn_arr_delay <- flights_tbl %>%
group_by(carrier, month) %>%
summarise(
mean_delay = mean(arr_delay),
mean_distance = mean(distance),
n = n()) %>%
collect() %>%
left_join(airlines) %>%
mutate(month = factor(month))
Joining, by = "carrier"
cr_mn_arr_delay
We now have 12 months of summaries for each of the 12 carriers. Given that we have more values and more variables, there are many ways we might visualize these summaries. In this case we will use a powerful method know variously as a facet plot, conditioned plot, trellis plot, or the method of small multiples.
A faceted or conditioned plot is comprised of a set of sub-plots defined by one or more conditioning variables. The data for each sub-plot is the result of a partitioning based on the values of the conditioning variable. This conditioning operation is, in effect, a group-by operation. This approach allows small multiples of a large complex dataset to be viewed in a systematic and understandable manner.
In effect, the method extends the number of dimensions projected onto a 2d computer display. This property makes conditioned plotting an idea tool for complex datasets with either many variables or records.
The idea of a facet plot has a long history. An early example of using small multiples was used to display some results from the 1870 US census. The plot below combines small multiples with a treemap plot to show proportions of the population in different occupations or attending school,
The small multiples idea was popularized in Edward Tufte’s 1983 book. Bill Cleveland and colleagues at AT&T Bell Labs created the Trellis plotting software package using the S language. Cleveland called this method Trellis Display.
The ggplot2 package contains the facet_grid function which is used to define the grid on which the sub-plots are created. The facet grid function uses an R formula object to define the rows and columns to specify the conditioning variable used to define the rows and columns. The general form of this formula is:
\[RowVariables \sim ColumnVariables\]
A conditioned plot with a single column, but multiple rows, is therefore defined:
\[RowVariables \sim\ .\]
Or, conditioned plot with a single row, but multiple columns, is defined:
\[.\ \sim ColumnVariables\]
You can use multiple variables to condition rows and columns, using the + symbol as the operator:
\[RowVar1 + RowVar2 + \ldots \sim ColVar1 + ColVar2 + \ldots\]
Like all good things in visualization, there are practical limits. Creating a large grid of sub-plots using multiple conditioning variables quickly becomes confusing to look at and understand. Best practice is to use one or two conditioning variables to start with and then to explore the dataset by changing one conditioning variable at a time.
The code in the cell below creates a faceted plot of monthly average flight delay by month. The data in these plots is grouped-by or conditioned on first the name of the airline and then the mean flight delay.
ggplot(cr_mn_arr_delay, aes(month, mean_delay, group = 1)) +
geom_point() +
geom_line() +
facet_grid(~ fct_reorder(name, mean_delay))

There is one plot for each airline, with the mean delay shown by month. These plots have been sorted by the mean delay by airline, so we can focus on the airlines with the greatest average delays. Notice that there are significant changes in the mean delays by month for each airline. Also notice that some airlines have very large jumps in mean delay in the summer months while it is not as pronounced for other airlines.
Your Turn: Next, let’s look at the relationship between the airlines and the number of flights.
- In the cell below create and execute the code to display the number of flights per month by airline sorted by mean flight delay.
- Create two plots, one on a log scale and one on a linear scale.
- Is this relationship useful in understanding this dataset?
- Hint, the ggplot2 attribute function
scale_y_log10() will create a plot with a log scale on the vertical axis.
top6 <- cr_arr_delay %>% filter(n > 400000) %>% .[["carrier"]]
top6
[1] "DL" "UA" "WN" "OO" "AA" "EV"
# overlay them all
cr_mn_arr_delay %>%
filter(carrier %in% top6) %>%
ggplot(aes(month, mean_delay, color = name, group = name)) +
geom_point() +
geom_line()

Visualizing Groups Without Faceting
The faceting examples above were useful in allowing us to examine average delays vs. month by carrier while allowing us to make visual comparisons across carriers. Often it is useful, instead of faceting, to overlay the data for the different groups in a single plot to make more relative comparisons between the groups.
Overlaying data from 12 airlines and trying to be able to visually distinguish between all of them is difficult, and this is one of the reasons faceting is such a good idea - it helps deal with overplotting.
We can sacrifice looking at all the data in a faceted plot to filtering out some of the data to be able to get a more clear picture in a single plot. Looking at the number of flights for each airline, there is a pretty clear separation between the bottom 6 and the top 6. Since the top 6 airlines account for 85% of all flights, they are probably the most interesting airlines to look at, so we will filter our data to compare the top 6 airlines in a single plot in a manageable way. The code in the cell below does the following:
- Filter out the small airlines by only keeping airlines with at least 400k flights in 2016.
- The pipeline for plotting the monthly flight delays does the following:
- The airlines are filtered for the ones with the large number of flights.
- A plot is created of the mean flight delay by month for the airlines with the largest numbers of flights.
top6 <- cr_arr_delay %>% filter(n > 400000) %>% .[["carrier"]]
top6
# overlay them all
cr_mn_arr_delay %>%
filter(carrier %in% top6) %>%
ggplot(aes(month, mean_delay, color = name, group = name)) +
geom_point() +
geom_line()
There appears to be a seasonal pattern to the mean delays for all of the top 6 carriers, which is similar for each airline. Of course, more years of data would help us more strongly support this conclusion. We see that in 2016, Delta typically had the best average on time performance, especially in the fall and early winter.
Your Turn: Let’s make the same plot for all airlines.
- Modify the code for the above plot to created a the plot faceted by whether the airline is in the top 6 or bottom 6. You will produce two line plots.
- Is there a difference in seasonal patterns or in general between airlines in the top 6 or bottom 6?
# group by, origin, dest, carrier, month and get mean delay and # obs
# and pull this back into R
route_summ = flights_tbl %>%
group_by(origin, dest, carrier, month) %>%
summarise(
mean_delay = mean(arr_delay),
n = n()) %>%
filter(n >= 25 & carrier %in% top6) %>%
collect()
route_summ
Further Drill Down with Trelliscope
We have seen an overall seasonal pattern for the top 6 airlines. Now, we are curious whether there is more to this very high-level summary.
Questions: - Are different flight routes more prone to delays? - does variability across airlines change for different routes?
We can visually investigate these questions by creating the same plot as above (mean delay vs. month with each carrier overlaid) for every route. As we will see, there are many routes, too many to look at all at once in a simple ggplot2 faceted plot. For this, we will turn to Trelliscope, which allows us to create large faceted displays and interactively navigate through the panels as we learn what is happening in each subset of the data.
We can get the data into shape for this task by grouping by route (origin and dest), month, and name. Since we are looking at the mean delay, and some routes are traveled more rarely, we want to make sure we have enough data to compute a meaningful statistic. Because of this, we’ll only look at routes that have, for a given route, carrier, and month, more than 50 flights.
We need to create a new grouping of the large dataset using sparklyr. The dplyr code in the cell below defines a sparklyr pipeline performing the following operations:
- Groups the data first by the flight origin, flight destination, carrier, and month.
- The mean delay and number of flights for each group are computed.
- Groups with fewer than 25 flights per month are filtered out.
- Results with airlines in the bottom 6 are filtered out.
- The results are collected back into your local R session.
route_summ2 <- route_summ %>%
left_join(airlines) %>%
rename(carrier_name = name) %>%
mutate(
carrier_name = factor(carrier_name),
month = factor(month))
Joining, by = "carrier"
route_summ2
We have gone from over 5.6 million rows to about 50k rows, two orders of magnitude reduction in size, and plenty small to now be working with in our local R session.
With a little more work, we can get this data more suitable for visualization.
- The airline names are joined.
- The airline names and months are converted to a factor variable which is helpful for making plots with ggplot2.
compl_routes <- route_summ2 %>%
group_by(origin, dest, carrier) %>%
summarise(n = n()) %>%
filter(n == 12) %>%
select(-n)
compl_routes
We have one more task to get the data into the state we need for visualization. For each route, we only want to plot data for airlines that recorded flights in all 12 months for that route. We can get a listing of all “complete” carrier/route combinations with the following:
route_summ3 <- right_join(route_summ2, compl_routes)
Joining, by = c("origin", "dest", "carrier")
route_summ3
There are over 3000 route / carrier combinations with a summary value for all 12 months. We can reduce our route summary data to just these combinations by joining compl_routes with route_summ2.
- We use
right_join() so that only route / carrier combinations in compl_routes are preserved.
- The join function automatically determines columns that the two data frames share and joins on them.
cat('Your code goes here')
Your code goes here
There are now about 38k summaries to visualize.
Your Turn: Remember that we want to visualize the mean delay vs. month for each carrier, faceted by route (origin and destination). Can you write some dplyr code to determine how many routes there are in our data?
filter(route_summ3, origin == "LAX" & dest == "JFK") %>%
ggplot(aes(month, mean_delay, color = carrier_name, group = carrier_name)) +
geom_point() +
geom_line() +
ylim(c(-39, 75.5)) +
scale_color_discrete(drop = FALSE)

There are about 2,669 routes for us to visualize. That’s a lot of plots! But we will see how we can easily handle this with Trelliscope.
First, let’s make sure the plot function we were using before works on one route.
mn_arr_delay <- flights_tbl %>%
group_by(month) %>%
summarise(mean_delay = mean(arr_delay)) %>%
collect() %>%
mutate(month = factor(month)) %>%
arrange(month)
mn_arr_delay
Since we will be making a lot of these plots, let’s also add in a reference line of the overall monthly mean.
filter(route_summ3, origin == "LAX" & dest == "JFK") %>%
ggplot(aes(month, mean_delay, color = carrier_name, group = carrier_name)) +
geom_line(aes(month, mean_delay), data = mn_arr_delay, color = "gray", size = 1, group = 1) +
geom_point() +
geom_line() +
ylim(c(-39, 75.5)) +
scale_color_discrete(drop = FALSE)

Now let’s add this to our plot.
filter(route_summ3, origin == "ATL") %>%
ggplot(aes(month, mean_delay, color = carrier_name, group = carrier_name)) +
geom_line(aes(month, mean_delay), data = mn_arr_delay, color = "gray", size = 1, group = 1) +
geom_point() +
geom_line() +
ylim(c(-31, 47)) +
scale_color_discrete(drop = FALSE) +
facet_trelliscope(~ origin + dest, nrow = 2, ncol = 4, path = "route_delay_atl")
** note: When inside an R Markdown document, the only way to embed aTrelliscope display within the notebook is to use self_contained = TRUE.
writing panels [=--------------------------------------] 1% 2/145 eta:19s
writing panels [=--------------------------------------] 2% 3/145 eta:27s
writing panels [=--------------------------------------] 3% 4/145 eta:29s
writing panels [=--------------------------------------] 3% 5/145 eta:31s
writing panels [==-------------------------------------] 4% 6/145 eta:32s
writing panels [==-------------------------------------] 5% 7/145 eta:33s
writing panels [==-------------------------------------] 6% 8/145 eta:33s
writing panels [==-------------------------------------] 6% 9/145 eta:34s
writing panels [===-----------------------------------] 7% 10/145 eta:34s
writing panels [===-----------------------------------] 8% 11/145 eta:35s
writing panels [===-----------------------------------] 8% 12/145 eta:35s
writing panels [===-----------------------------------] 9% 13/145 eta:34s
writing panels [====----------------------------------] 10% 14/145 eta:34s
writing panels [====----------------------------------] 10% 15/145 eta:34s
writing panels [====----------------------------------] 11% 16/145 eta:34s
writing panels [====----------------------------------] 12% 17/145 eta:34s
writing panels [=====---------------------------------] 12% 18/145 eta:34s
writing panels [=====---------------------------------] 13% 19/145 eta:34s
writing panels [=====---------------------------------] 14% 20/145 eta:34s
writing panels [======--------------------------------] 14% 21/145 eta:33s
writing panels [======--------------------------------] 15% 22/145 eta:33s
writing panels [======--------------------------------] 16% 23/145 eta:33s
writing panels [======--------------------------------] 17% 24/145 eta:33s
writing panels [=======-------------------------------] 17% 25/145 eta:32s
writing panels [=======-------------------------------] 18% 26/145 eta:32s
writing panels [=======-------------------------------] 19% 27/145 eta:32s
writing panels [=======-------------------------------] 19% 28/145 eta:32s
writing panels [========------------------------------] 20% 29/145 eta:32s
writing panels [========------------------------------] 21% 30/145 eta:31s
writing panels [========------------------------------] 21% 31/145 eta:31s
writing panels [========------------------------------] 22% 32/145 eta:31s
writing panels [=========-----------------------------] 23% 33/145 eta:31s
writing panels [=========-----------------------------] 23% 34/145 eta:30s
writing panels [=========-----------------------------] 24% 35/145 eta:30s
writing panels [=========-----------------------------] 25% 36/145 eta:30s
writing panels [==========----------------------------] 26% 37/145 eta:30s
writing panels [==========----------------------------] 26% 38/145 eta:30s
writing panels [==========----------------------------] 27% 39/145 eta:29s
writing panels [==========----------------------------] 28% 40/145 eta:29s
writing panels [===========---------------------------] 28% 41/145 eta:29s
writing panels [===========---------------------------] 29% 42/145 eta:28s
writing panels [===========---------------------------] 30% 43/145 eta:28s
writing panels [============--------------------------] 30% 44/145 eta:28s
writing panels [============--------------------------] 31% 45/145 eta:28s
writing panels [============--------------------------] 32% 46/145 eta:27s
writing panels [============--------------------------] 32% 47/145 eta:27s
writing panels [=============-------------------------] 33% 48/145 eta:27s
writing panels [=============-------------------------] 34% 49/145 eta:27s
writing panels [=============-------------------------] 34% 50/145 eta:26s
writing panels [=============-------------------------] 35% 51/145 eta:26s
writing panels [==============------------------------] 36% 52/145 eta:26s
writing panels [==============------------------------] 37% 53/145 eta:25s
writing panels [==============------------------------] 37% 54/145 eta:25s
writing panels [==============------------------------] 38% 55/145 eta:25s
writing panels [===============-----------------------] 39% 56/145 eta:25s
writing panels [===============-----------------------] 39% 57/145 eta:24s
writing panels [===============-----------------------] 40% 58/145 eta:24s
writing panels [===============-----------------------] 41% 59/145 eta:24s
writing panels [================----------------------] 41% 60/145 eta:24s
writing panels [================----------------------] 42% 61/145 eta:23s
writing panels [================----------------------] 43% 62/145 eta:23s
writing panels [=================---------------------] 43% 63/145 eta:23s
writing panels [=================---------------------] 44% 64/145 eta:22s
writing panels [=================---------------------] 45% 65/145 eta:22s
writing panels [=================---------------------] 46% 66/145 eta:22s
writing panels [==================--------------------] 46% 67/145 eta:22s
writing panels [==================--------------------] 47% 68/145 eta:21s
writing panels [==================--------------------] 48% 69/145 eta:21s
writing panels [==================--------------------] 48% 70/145 eta:21s
writing panels [===================-------------------] 49% 71/145 eta:21s
writing panels [===================-------------------] 50% 72/145 eta:20s
writing panels [===================-------------------] 50% 73/145 eta:20s
writing panels [===================-------------------] 51% 74/145 eta:20s
writing panels [====================------------------] 52% 75/145 eta:20s
writing panels [====================------------------] 52% 76/145 eta:19s
writing panels [====================------------------] 53% 77/145 eta:19s
writing panels [====================------------------] 54% 78/145 eta:19s
writing panels [=====================-----------------] 54% 79/145 eta:19s
writing panels [=====================-----------------] 55% 80/145 eta:18s
writing panels [=====================-----------------] 56% 81/145 eta:18s
writing panels [=====================-----------------] 57% 82/145 eta:18s
writing panels [======================----------------] 57% 83/145 eta:17s
writing panels [======================----------------] 58% 84/145 eta:17s
writing panels [======================----------------] 59% 85/145 eta:17s
writing panels [=======================---------------] 59% 86/145 eta:17s
writing panels [=======================---------------] 60% 87/145 eta:16s
writing panels [=======================---------------] 61% 88/145 eta:16s
writing panels [=======================---------------] 61% 89/145 eta:16s
writing panels [========================--------------] 62% 90/145 eta:16s
writing panels [========================--------------] 63% 91/145 eta:15s
writing panels [========================--------------] 63% 92/145 eta:15s
writing panels [========================--------------] 64% 93/145 eta:15s
writing panels [=========================-------------] 65% 94/145 eta:14s
writing panels [=========================-------------] 66% 95/145 eta:14s
writing panels [=========================-------------] 66% 96/145 eta:14s
writing panels [=========================-------------] 67% 97/145 eta:14s
writing panels [==========================------------] 68% 98/145 eta:13s
writing panels [==========================------------] 68% 99/145 eta:13s
writing panels [==========================-----------] 69% 100/145 eta:13s
writing panels [==========================-----------] 70% 101/145 eta:12s
writing panels [==========================-----------] 70% 102/145 eta:12s
writing panels [==========================-----------] 71% 103/145 eta:12s
writing panels [===========================----------] 72% 104/145 eta:12s
writing panels [===========================----------] 72% 105/145 eta:11s
writing panels [===========================----------] 73% 106/145 eta:11s
writing panels [===========================----------] 74% 107/145 eta:11s
writing panels [============================---------] 74% 108/145 eta:10s
writing panels [============================---------] 75% 109/145 eta:10s
writing panels [============================---------] 76% 110/145 eta:10s
writing panels [============================---------] 77% 111/145 eta:10s
writing panels [=============================--------] 77% 112/145 eta: 9s
writing panels [=============================--------] 78% 113/145 eta: 9s
writing panels [=============================--------] 79% 114/145 eta: 9s
writing panels [=============================--------] 79% 115/145 eta: 8s
writing panels [==============================-------] 80% 116/145 eta: 8s
writing panels [==============================-------] 81% 117/145 eta: 8s
writing panels [==============================-------] 81% 118/145 eta: 8s
writing panels [==============================-------] 82% 119/145 eta: 7s
writing panels [===============================------] 83% 120/145 eta: 7s
writing panels [===============================------] 83% 121/145 eta: 7s
writing panels [===============================------] 84% 122/145 eta: 7s
writing panels [===============================------] 85% 123/145 eta: 6s
writing panels [================================-----] 86% 124/145 eta: 6s
writing panels [================================-----] 86% 125/145 eta: 6s
writing panels [================================-----] 87% 126/145 eta: 5s
writing panels [================================-----] 88% 127/145 eta: 5s
writing panels [=================================----] 88% 128/145 eta: 5s
writing panels [=================================----] 89% 129/145 eta: 5s
writing panels [=================================----] 90% 130/145 eta: 4s
writing panels [=================================----] 90% 131/145 eta: 4s
writing panels [==================================---] 91% 132/145 eta: 4s
writing panels [==================================---] 92% 133/145 eta: 3s
writing panels [==================================---] 92% 134/145 eta: 3s
writing panels [==================================---] 93% 135/145 eta: 3s
writing panels [===================================--] 94% 136/145 eta: 3s
writing panels [===================================--] 94% 137/145 eta: 2s
writing panels [===================================--] 95% 138/145 eta: 2s
The gray line gives us a nice reference point for how the route we are looking at compares to the overall mean monthly delay.
Now, after all this munging, we are finally ready to create a Trelliscope display. Fortunately, creating a trelliscope display is extremely easy. All we need to do is add a faceting directive to our ggplot code. But here we use the function facet_trelliscope().
filter(route_summ3, origin == "ATL") %>%
ggplot(aes(month, mean_delay, color = carrier_name, group = carrier_name)) +
geom_line(aes(month, mean_delay), data = mn_arr_delay, color = "gray", size = 1, group = 1) +
geom_point() +
geom_line() +
ylim(c(-31, 47)) +
scale_color_discrete(drop = FALSE) +
facet_trelliscope(~ origin + dest, nrow = 2, ncol = 4, path = "route_delay_atl")
If the above code snippet doesn’t open up a web browser with the resulting plot, you can view it in your web browser with the following command:
nycflights13::airports
When the display opens, you should see something like this:
This is an interactive faceted display that opens in your web browser. There are about 150 routes out of Atlanta that fit our criteria. ~150 panels is too many to display at once, which is why the display is showing the first 8, by default ordered alphabetically by our grouping variables, origin and destination airport code.
Here are some simple interactions you can do with the Trelliscope display:
- Use the “Prev” and “Next” buttons, or your left and right arrow keys, to page through the panels of the display. This simple quick navigation helps you scan and find patterns that stick out.
- Click the “Grid” button on the sidebar and change the number of rows and columns that are being displayed per page.
- Click the “Sort” button on the sidebar to change the variables to order the panels according to.
- You can click the “x” icon next to the existing sort variables (“origin” and “dest”) to remove the sorting on that variable, and then choose a new variable from the list to sort on. The blue icon next the variable name allows you to toggle the order from increasing to decreasing.
You will notice some unfamiliar variables in this list, such as mean_delay_mean. Trelliscope inspects the data that you pass in to your ggplot command and automatically computes per-subset metrics that it thinks might be interesting for you to navigate the panels with. One of the variables in our input data, route_summ3, is mean_delay. Trelliscope took this variable and computed the average mean delay for each observation in each route and made it available as a metric to sort and filter on, mean_delay_mean. We call these metrics cognostics. You can sort on the mean_delay_mean cognostic to see what the most consistently early and late routes and airlines are. - Click the “Filter” button on the sidebar to filter the panels to be displayed. A list of cognostic variables is shown and you can select one to filter on. For example , if you select mean_delay_mean, an interactive histogram showing the distribution of this variable appears and you can select panels that, for example, have a negative mean delay. - Click the “Labels” button on the sidebar to control which labels are displayed underneath each panel.
Your Turn: Investigate the Trelliscope display and try to answer the following questions: - Can you find some odd or unexpected behavior in this display? - Do any of the routes seem to follow the seasonal pattern we saw in our aggregate plot? - Did subsetting the data by route provide any additional interesting insights about the data? - What other ways might you think of dividing or visualizing this data that might be interesting? ***
Adding Cognostics to Displays
In our previous display, we didn’t have too many variables to filter or sort our panels by, so we may want to add more to our display.
One piece of data we can use to augment our display is metadata about the airports. The nycflights13 R package contains such a dataset.
dest_airports <- nycflights13::airports %>%
rename(dest = faa, airport_name = name, dest_lat = lat, dest_lon = lon, dest_alt = alt,
dest_tzone = tzone) %>%
select(-c(tz, dst))
dest_airports
Since we only are looking at a single origin airport, let’s add airport metadata for our destinations. Let’s select a few variables from this dataset and rename the variables to be more meaningful for being applied to destination airports.
route_summ4 <- left_join(route_summ3, dest_airports)
Joining, by = "dest"
route_summ4
The variable that matches this data to our route_summ3 data is dest, so we can simply join on that to get a new dataset:
route_summ4 <- route_summ4 %>%
group_by(origin, dest) %>%
mutate(min_delay = min(mean_delay), max_delay = max(mean_delay))
route_summ4
Suppose we also want to be able to sort routes according to the absolute earliest and latest arrival across all airlines and months. To do this, we can add new summary columns to our data:
filter(route_summ4, origin == "ATL") %>%
ggplot(aes(month, mean_delay, color = carrier_name, group = carrier_name)) +
geom_line(aes(month, mean_delay), data = mn_arr_delay, color = "gray", size = 1, group = 1) +
geom_point() +
geom_line() +
ylim(c(-31, 47)) +
scale_color_discrete(drop = FALSE) +
facet_trelliscope(~ origin + dest, nrow = 2, ncol = 4, path = "route_delay_atl2")
** note: When inside an R Markdown document, the only way to embed aTrelliscope display within the notebook is to use self_contained = TRUE.
writing panels [=--------------------------------------] 1% 2/145 eta:27s
writing panels [=--------------------------------------] 2% 3/145 eta:32s
writing panels [=--------------------------------------] 3% 4/145 eta:34s
writing panels [=--------------------------------------] 3% 5/145 eta:35s
writing panels [==-------------------------------------] 4% 6/145 eta:35s
writing panels [==-------------------------------------] 5% 7/145 eta:36s
writing panels [==-------------------------------------] 6% 8/145 eta:36s
writing panels [==-------------------------------------] 6% 9/145 eta:36s
writing panels [===-----------------------------------] 7% 10/145 eta:36s
writing panels [===-----------------------------------] 8% 11/145 eta:36s
writing panels [===-----------------------------------] 8% 12/145 eta:36s
writing panels [===-----------------------------------] 9% 13/145 eta:36s
writing panels [====----------------------------------] 10% 14/145 eta:36s
writing panels [====----------------------------------] 10% 15/145 eta:35s
writing panels [====----------------------------------] 11% 16/145 eta:35s
writing panels [====----------------------------------] 12% 17/145 eta:35s
writing panels [=====---------------------------------] 12% 18/145 eta:35s
writing panels [=====---------------------------------] 13% 19/145 eta:35s
writing panels [=====---------------------------------] 14% 20/145 eta:35s
writing panels [======--------------------------------] 14% 21/145 eta:34s
writing panels [======--------------------------------] 15% 22/145 eta:34s
writing panels [======--------------------------------] 16% 23/145 eta:34s
writing panels [======--------------------------------] 17% 24/145 eta:34s
writing panels [=======-------------------------------] 17% 25/145 eta:34s
writing panels [=======-------------------------------] 18% 26/145 eta:34s
writing panels [=======-------------------------------] 19% 27/145 eta:33s
writing panels [=======-------------------------------] 19% 28/145 eta:33s
writing panels [========------------------------------] 20% 29/145 eta:33s
writing panels [========------------------------------] 21% 30/145 eta:33s
writing panels [========------------------------------] 21% 31/145 eta:32s
writing panels [========------------------------------] 22% 32/145 eta:32s
writing panels [=========-----------------------------] 23% 33/145 eta:32s
writing panels [=========-----------------------------] 23% 34/145 eta:32s
writing panels [=========-----------------------------] 24% 35/145 eta:31s
writing panels [=========-----------------------------] 25% 36/145 eta:31s
writing panels [==========----------------------------] 26% 37/145 eta:31s
writing panels [==========----------------------------] 26% 38/145 eta:31s
writing panels [==========----------------------------] 27% 39/145 eta:31s
writing panels [==========----------------------------] 28% 40/145 eta:30s
writing panels [===========---------------------------] 28% 41/145 eta:30s
writing panels [===========---------------------------] 29% 42/145 eta:30s
writing panels [===========---------------------------] 30% 43/145 eta:29s
writing panels [============--------------------------] 30% 44/145 eta:29s
writing panels [============--------------------------] 31% 45/145 eta:29s
writing panels [============--------------------------] 32% 46/145 eta:28s
writing panels [============--------------------------] 32% 47/145 eta:28s
writing panels [=============-------------------------] 33% 48/145 eta:28s
writing panels [=============-------------------------] 34% 49/145 eta:28s
writing panels [=============-------------------------] 34% 50/145 eta:27s
writing panels [=============-------------------------] 35% 51/145 eta:27s
writing panels [==============------------------------] 36% 52/145 eta:27s
writing panels [==============------------------------] 37% 53/145 eta:26s
writing panels [==============------------------------] 37% 54/145 eta:26s
writing panels [==============------------------------] 38% 55/145 eta:26s
writing panels [===============-----------------------] 39% 56/145 eta:25s
writing panels [===============-----------------------] 39% 57/145 eta:25s
writing panels [===============-----------------------] 40% 58/145 eta:25s
writing panels [===============-----------------------] 41% 59/145 eta:25s
writing panels [================----------------------] 41% 60/145 eta:24s
writing panels [================----------------------] 42% 61/145 eta:24s
writing panels [================----------------------] 43% 62/145 eta:24s
writing panels [=================---------------------] 43% 63/145 eta:24s
writing panels [=================---------------------] 44% 64/145 eta:23s
writing panels [=================---------------------] 45% 65/145 eta:23s
writing panels [=================---------------------] 46% 66/145 eta:23s
writing panels [==================--------------------] 46% 67/145 eta:22s
writing panels [==================--------------------] 47% 68/145 eta:22s
writing panels [==================--------------------] 48% 69/145 eta:22s
writing panels [==================--------------------] 48% 70/145 eta:22s
writing panels [===================-------------------] 49% 71/145 eta:21s
writing panels [===================-------------------] 50% 72/145 eta:21s
writing panels [===================-------------------] 50% 73/145 eta:21s
writing panels [===================-------------------] 51% 74/145 eta:20s
writing panels [====================------------------] 52% 75/145 eta:20s
writing panels [====================------------------] 52% 76/145 eta:20s
writing panels [====================------------------] 53% 77/145 eta:20s
writing panels [====================------------------] 54% 78/145 eta:19s
writing panels [=====================-----------------] 54% 79/145 eta:19s
writing panels [=====================-----------------] 55% 80/145 eta:19s
writing panels [=====================-----------------] 56% 81/145 eta:18s
writing panels [=====================-----------------] 57% 82/145 eta:18s
writing panels [======================----------------] 57% 83/145 eta:18s
writing panels [======================----------------] 58% 84/145 eta:18s
writing panels [======================----------------] 59% 85/145 eta:17s
writing panels [=======================---------------] 59% 86/145 eta:17s
writing panels [=======================---------------] 60% 87/145 eta:17s
writing panels [=======================---------------] 61% 88/145 eta:16s
writing panels [=======================---------------] 61% 89/145 eta:16s
writing panels [========================--------------] 62% 90/145 eta:16s
writing panels [========================--------------] 63% 91/145 eta:16s
writing panels [========================--------------] 63% 92/145 eta:15s
writing panels [========================--------------] 64% 93/145 eta:15s
writing panels [=========================-------------] 65% 94/145 eta:15s
writing panels [=========================-------------] 66% 95/145 eta:15s
writing panels [=========================-------------] 66% 96/145 eta:14s
writing panels [=========================-------------] 67% 97/145 eta:14s
writing panels [==========================------------] 68% 98/145 eta:14s
writing panels [==========================------------] 68% 99/145 eta:13s
writing panels [==========================-----------] 69% 100/145 eta:13s
writing panels [==========================-----------] 70% 101/145 eta:13s
writing panels [==========================-----------] 70% 102/145 eta:12s
writing panels [==========================-----------] 71% 103/145 eta:12s
writing panels [===========================----------] 72% 104/145 eta:12s
writing panels [===========================----------] 72% 105/145 eta:12s
writing panels [===========================----------] 73% 106/145 eta:11s
writing panels [===========================----------] 74% 107/145 eta:11s
writing panels [============================---------] 74% 108/145 eta:11s
writing panels [============================---------] 75% 109/145 eta:10s
writing panels [============================---------] 76% 110/145 eta:10s
writing panels [============================---------] 77% 111/145 eta:10s
writing panels [=============================--------] 77% 112/145 eta:10s
writing panels [=============================--------] 78% 113/145 eta: 9s
writing panels [=============================--------] 79% 114/145 eta: 9s
writing panels [=============================--------] 79% 115/145 eta: 9s
writing panels [==============================-------] 80% 116/145 eta: 8s
writing panels [==============================-------] 81% 117/145 eta: 8s
writing panels [==============================-------] 81% 118/145 eta: 8s
writing panels [==============================-------] 82% 119/145 eta: 8s
writing panels [===============================------] 83% 120/145 eta: 7s
writing panels [===============================------] 83% 121/145 eta: 7s
writing panels [===============================------] 84% 122/145 eta: 7s
writing panels [===============================------] 85% 123/145 eta: 6s
writing panels [================================-----] 86% 124/145 eta: 6s
writing panels [================================-----] 86% 125/145 eta: 6s
writing panels [================================-----] 87% 126/145 eta: 6s
Now that we have more variables in our data, let’s recreate our Trelliscope display. The only thing we change here is the input dataset, and we place the resulting display in a different directory than our previous one.
filter(route_summ4, origin == "ATL") %>%
ggplot(aes(month, mean_delay, color = carrier_name, group = carrier_name)) +
geom_line(aes(month, mean_delay), data = mn_arr_delay, color = "gray", size = 1, group = 1) +
geom_point() +
geom_line() +
ylim(c(-31, 47)) +
scale_color_discrete(drop = FALSE) +
facet_trelliscope(~ origin + dest, nrow = 2, ncol = 4, path = "route_delay_atl2")
Again, if this display didn’t display in your browser as a result of running the previous cell, run the following:
browseURL("route_delay_atl2/index.html")
Now we can see if these new cognostics provide us any more meaningful ways to navigate or understand our data.
Your Turn: Investigate the Trelliscope display and try to answer the following questions: - Can you turn on the dest_name label to give you a helpful hint as to where or what the destination airport code is referring to? - What is the most common destination time zone for flights out of Atlanta? hint: turn on the dest_tzone filter and look at its distribution. - What are the worst destination time zones in terms of more delayed flights? hint: in addition to turning on the dest_tzone filter, turn on the mean_delay_mean filter and then examine how the distribution of this filter changes as you make different selections of the dest_tzone filter. - Which route has the worst delay time? hint: sort on min_delay. ***
Larger Trelliscope Displays
It is worth noting that although we are only looking at ~150 plots in the above Trellscope displays, the notion of Trelliscope display conceptually scales to displays of a very large number of panels. We have made displays with panels numbering in the millions. Even though you may have a million subsets of data available to look at, it does not mean that you have to look at all of it, and using an interactive viewer like Trelliscope with cognostics that guide you to interesting areas of your data, it becomes a powerful, flexible, detailed exploratory visualization tool.
For this example, a much more interesting display would be to plot all ~2700 routes in a single display. Currently, trelliscopejs pre-renders all the plots, and since ggplot2 is a bit slow, it would take 10-15 minutes to generate the display. Even with rendering on-the-fly, which was supported in the previous Shiny-powered Trelliscope package, and which will be supported in trelliscopejs soon, ggplot2 can be too slow for a good user experience. Other plotting packages can be used with trelliscopejs. Some packages render much more quickly. We provide pointers to these packages in the next section.
A final point to make about larger Trelliscope displays. The datadr package supports use of arbitrary data structures and R code on a Hadoop cluster. You can generate trelliscopejs displays directly against a very large dataset on the cluster. You can find a tutorial on these techniquesfrom last year’s Strata. In the future, it is our vision for sparklyr to support this capability. You will then have a interactive window into large datasets with sparklyr.
Trelliscope in the Tidyverse
As mentioned in the previous section, if you don’t want to use ggplot2 to generate panels, you are welcome to use other plotting librarys, including conceptually any htmlwidget to generate the panels of your display. This goes beyond the scope and timing of this tutorial, but a good reference on this can be found here.
Your Turn: Create another Trelliscope display showing all routes that originate from your favorite airport, or that originate or have destinations at various airports of interest.
cat('Your code goes here')
LS0tCnRpdGxlOiAiRXhwbG9yYXRpb24gYW5kIHZpc3VhbGl6YXRpb24gb2YgbGFyZ2UsIGNvbXBsZXggZGF0YXNldHMgd2l0aCBSLCBIYWRvb3AsIGFuZCBTcGFyayIKYXV0aG9yOiBTdGVwaGVuIEVsc3RvbiBhbmQgUnlhbiBIYWZlbgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBJbnN0YWxsYXRpb24gZGlyZWN0aW9ucwoKQmVmb3JlIHlvdSBjYW4gcnVuIHRoZSBjb2RlIGluIHRoaXMgbm90ZWJvb2sgeW91IHNob3VsZCBmb2xsb3cgdGhlIGRpcmVjdGlvbnMgZnJvbSB0aGUgW1JFQURNRV0oaHR0cHM6Ly9naXRodWIuY29tL2hhZmVuL3N0cmF0YTIwMTcpLgoKIyMgT3ZlcnZpZXcKCkluIHRoaXMgdHV0b3JpYWwgd2Ugd2lsbCBleHBsb3JlIG1ldGhvZHMgZm9yIGV4cGxvcmF0aW9uIGFuZCB2aXN1YWxpemF0aW9uIG9mIGxhcmdlIGNvbXBsZXggZGF0YXNldHMgdXNpbmcgUiBhbmQgU3BhcmsuIFdlIHdpbGwgY292ZXIgdGhlIGZvbGxvd2luZyB0b3BpY3M6CgotIERldmVsb3Bpbmcgc2tpbGxzIGZvciBleHBsb3JpbmcgZGF0YSBpbiBhbiBpdGVyYXRpdmUgZmFzaGlvbi4gU2luY2UgaXQgaXMgaW1wb3NzaWJsZSB0byBwcmVkaWN0IHdoaWNoIHZpZXdzIGFuZCBzdW1tYXJpZXMgb2YgYSBuZXcgZGF0YXNldCBhcmUgdGhlIG1vc3QgaW50ZXJlc3RpbmcsIGFuIGl0ZXJhdGl2ZSBwcm9jZXNzIGlzIHJlcXVpcmVkLiBUaGVyZWZvcmUsIGl0IGlzIGltcG9ydGFudCB0byBjcmVhdGUgYW5kIHVuZGVyc3RhbmQgbXVsdGlwbGUgdmlld3Mgb2YgeW91ciBkYXRhLgotIFVzaW5nIHRoZSBkaXZpZGUgYW5kIHJlY29tYmluZSBtZXRob2RvbG9neSBvbiBkYXRhIHRvIGNvbXB1dGUgc3VtbWFyeSBzdGF0aXN0aWNzIG9yIHByZXBhcmUgZm9yIHZpc3VhbGl6YXRpb24uCi0gV29ya2luZyB3aXRoIFNwYXJrIGFzIGEgc2NhbGFibGUgYmFjay1lbmQgZm9yIGRpdmlkZSBhbmQgcmVjb21iaW5lLgotIFBsb3R0aW5nIGNvbXBsZXggZGF0YSwgZXNwZWNpYWxseSB1c2luZyB0aGUgdGhlICBtZXRob2Qgb2Ygc21hbGwgbXVsdGlwbGVzIG9yIGNvbmRpdGlvbmluZy4KClRoaXMgdHV0b3JpYWwgaXMgbWFpbmx5IGFib3V0IHZpc3VhbGl6YXRpb24sIHJhbmdpbmcgZnJvbSBzdW1tYXJpZXMgdG8gbW9yZSBkZXRhaWxlZCB2aWV3cyBvZiB0aGUgZGF0YS4gQSBtYWpvciBjb21wb25lbnQgb2YgY3JlYXRpbmcgdmlzdWFsaXphdGlvbnMgLS0gbGFyZ2Ugb3Igc21hbGwgLS0gZnJvbSBiaWcgZGF0YSBpcyB0aGF0IHRoZXJlIGlzIGEgbG90IG9mIGRhdGEgbWFuaXB1bGF0aW9uIGludm9sdmVkLiBDb25zZXF1ZW50bHksIGEgZ29vZCBkZWFsIG9mIHRoZSB0dXRvcmlhbCBpcyBzcGVudCBpbGx1c3RyYXRpbmcgaG93IHRvIHBlcmZvcm0gYSB3aWRlIHZhcmlldHkgb2Ygb3BlcmF0aW9ucyBvbiBkYXRhIHRvIGdldCBpdCBpbnRvIHNoYXBlIGZvciB2aXN1YWxpemF0aW9uLgoKIyMgSW50cm9kdWN0aW9uIHRvIGRpdmlkZSBhbmQgcmVjb21iaW5lCgpUaGUgKipkaXZpZGUgYW5kIHJlY29tYmluZSoqIG9yICoqRCZSKiogbWV0aG9kIHByb3ZpZGVzIGEgaGlnaGx5IHNjYWxhYmxlIGFwcHJvYWNoIHRvIGFuYWx5c2lzIG9mIGxhcmdlIGNvbXBsZXggZGF0YXNldHMuIFdpdGggRCZSIHdlIHdvcmsgd2l0aCBtZWFuaW5nZnVsLCBwZXJzaXN0ZW50IGRpdmlzaW9ucyBvZiB0aGUgZGF0YS4gIkJpZyBkYXRhIiBpcyB0eXBpY2FsbHkgYmlnIGJlY2F1c2UgaXQgaXMgbWFkZSB1cCBvZiBjb2xsZWN0aW9ucyBvZiBtYW55IHN1YnNldHMsIHNlbnNvcnMsIGxvY2F0aW9ucywgdGltZSBwZXJpb2RzLCBldGMuIEEgc2NoZW1hdGljIHZpZXcgb2YgdGhlIEQmUiBwcm9jZXNzIGlzIHNob3duIGluIHRoZSBmaWd1cmUgYmVsb3cuCgohW10oaW1hZ2VzL2RyZGlhZ3JhbS5wbmcpCgpUaGVyZSBhcmUgbWFueSBwb3NzaWJsZSB3YXlzIHRvICoqZGl2aWRlIGRhdGEqKi4gVGhlIGJlc3QgY2hvaWNlIGRlcGVuZHMgb24gdGhlIG5hdHVyZSBvZiB0aGUgZGF0YSBhbmQgdGhlIGFuYWx5c2lzIHRvIGJlIHBlcmZvcm1lZC4gU29tZSBwb3NzaWJpbGl0aWVzIGluY2x1ZGU6CgotIEJyZWFrIHRoZSBkYXRhIHVwIGJhc2VkIG9uIGRhdGEgc3RydWN0dXJlIGFuZCBhcHBseSB2aXN1YWwgb3IgYW5hbHl0aWNhbCBtZXRob2RzLiBXZSBjYWxsIHRoaXMgY29uZGl0aW9uaW5nIHZhcmlhYmxlIGRpdmlzaW9uLiBJbiBwcmFjdGljZSB0aGlzIGFwcHJvYWNoIGlzIGNvbW1vbiBhbmQgbm90IG5ldy4KLSBBbm90aGVyIG9wdGlvbiBpcyByYW5kb20gcmVwbGljYXRlIGRpdmlzaW9uCgpPbmNlIHRoZSBkYXRhIGFyZSBkaXZpZGVkLCBhbmFseXRpYyBvciB2aXN1YWwgbWV0aG9kcyBhcmUgYXBwbGllZCBpbmRlcGVuZGVudGx5IHRvIGVhY2ggc3Vic2V0IGluIGFuICoqZW1iYXJyYXNzaW5nbHkgcGFyYWxsZWwqKiBmYXNoaW9uLiBUaGUgcmVzdWx0cyBvZiB0aGVzZSBhbmFseXNlcyBhcmUgKipyZWNvbWJpbmVkKiogdG8geWllbGQgYSBzdGF0aXN0aWNhbGx5IHZhbGlkIEQmUiByZXN1bHQgb3IgdmlzdWFsaXphdGlvbi4gV2UgcmVmZXIgdG8gdGhlc2Ugb3B0aW9ucyBhczoKCi0gQW5hbHl0aWMgcmVjb21iaW5hdGlvbgotIFN1bW1hcnkgb3IgYWdncmVnYXRpb24gcmVjb21iaW5hdGlvbgotIEdyYXBoaWNhbCByZWNvbWJpbmF0aW9uCgpJbiB0aGlzIGxlc3Nvbiwgb3VyIGZvY3VzIGlzIG9uIHN1bW1hcnkgYW5kIGdyYXBoaWNhbCByZWNvbWJpbmF0aW9uIGZvciB0aGUgZXhwbG9yYXRpb24gb2YgbGFyZ2UgY29tcGxleCBkYXRhc2V0cy4KCiMjIEJpZyBEYXRhIHdpdGggUiBhbmQgU3BhcmsKClRoaXMgdHV0b3JpYWwgZm9jdXNlcyBvbiB0aGUgZXhwbG9yYXRpb24gYW5kIHZpc3VhbGl6YXRpb24gb2YgbGFyZ2UgY29tcGxleCBkYXRhc2V0cyB1c2luZyB0aGUgRCZSIHBhcmFkaWdtLiBUbyBkbyBzbywgd2UgbmVlZCBhIG1hc3NpdmVseSBzY2FsYWJsZSBiYWNrLWVuZCB0byBwZXJmb3JtIHRoZSBsYXJnZSBzY2FsZSBkYXRhIG9wZXJhdGlvbnMuIEluIHRoaXMgY2FzZSB3ZSBhcmUgdXNpbmcgYSBTcGFyayBiYWNrLWVuZC4gVGhlIGFyY2hpdGVjdHVyZSBvdXIgZW52aXJvbm1lbnQgaXMgc2hvd24gc2NoZW1hdGljYWxseSBpbiB0aGUgZmlndXJlIGJlbG93LgoKIVtdKGltYWdlcy9zcGFya2x5ci5qcGcpCgpUaGUgY29tcG9uZW50cyBvZiB0aGUgYXJjaGl0ZWN0dXJlIGFyZToKCi0gU3BhcmsgYmFjay1lbmQgcGVyZm9ybXMgdGhlIGxhcmdlLXNjYWxlIGRpdmlkZSBhbmQgcmVjb21iaW5lIG9wZXJhdGlvbnMuCiAgLSBTcGFyayBjYW4gYmUgcnVuIGxvY2FsbHkgYXMgd2UgZG8gaW4gdGhpcyB0dXRvcmlhbCBvciBvbiBhIG1hc3NpdmUgY2x1c3Rlci4gQSBIYWRvb3AgY2x1c3RlciBvciBzb21lIG90aGVyIHNjYWxhYmxlIGJhY2sgZW5kIGNhbiBiZSB1c2VkIHVzZWQuCiAgLSBUaGUgRCZSIG9wZXJhdGlvbnMgYXJlIHBlcmZvcm1lZCB3aXRoaW4gYSBTcGFyayB0cmFuc2Zvcm0gcGlwZWxpbmUgaW4gdGhlIFNwYXJrIHNlc3Npb24uCiAgLSBTcGFyayB1c2VzIGhpZ2hseSBzY2FsYWJsZSBzdG9yYWdlIG9wdGlvbnMsIHN1Y2ggYXMgSERGUy4KLSBzcGFya2x5ciBwcm92aWRlcyBzZXNzaW9uIG1hbmFnZW1lbnQgYW5kIHRyYW5zZm9ybSBvcmNoZXN0cmF0aW9uIGZvciBTcGFyay4KLSBBIGxvY2FsIFIgc2Vzc2lvbiBydW5uaW5nIHNwYXJrbHlyIGFuZCBhbnkgb3RoZXIgcmVxdWlyZWQgcGFja2FnZXMgY29udHJvbHMgdGhlIGVudmlyb25tZW50LiBzcGFya2x5ciB0cmFuc2xhdGVzIHRoZSBkYXRhIG11bmdpbmcgcGlwZWxpbmUgZGVmaW5lZCBpbiBSIGludG8gYSB0cmFuc2Zvcm1hdGlvbiBwaXBlbGluZSBpbiBTcGFyay4KCiMjIE90aGVyIEQmUiBBcmNoaXRlY3R1cmVzICMjCgpJdCBpcyB1c2VmdWwgdG8gZGlzY3VzcyBzb21lIG9mIHRoZSBsaW1pdGF0aW9ucyBvZiB0aGlzIGFyY2hpdGVjdHVyZSB3aXRoIHJlc3BlY3QgdG8gdGhlIEQmUiBwYXJhZGlnbSBhbmQgY29tcGFyZSBpdCB0byBvdGhlciBEJlIgc29mdHdhcmUgYXZhaWxhYmxlIHRvIHVuZGVyc3RhbmQgd2hpY2ggYXJjaGl0ZWN0dXJlIGlzIGFwcHJvcHJpYXRlIGZvciBkaWZmZXJlbnQgc2l0dWF0aW9ucyBhbmQgdG8gZGlzY3VzcyB3aGF0IHdlIGVudmlzaW9uIGFzIHRoZSBmdXR1cmUgb2YgYW4gaWRlYWwgRCZSIGFyY2hpdGVjdHVyZS4KClRoZSBEJlIgcHJvamVjdCBvcmlnaW5hdGVkIGFzIGFuIFIgZnJvbnQtZW5kIHRvIEhhZG9vcCwgY2FsbGVkIFJISVBFLCB0aGUgUiBhbmQgSGFkb29wIEludGVncmF0ZWQgUHJvZ3JhbW1pbmcgRW52aXJvbm1lbnQuIFRoaXMgUiBwYWNrYWdlIGFsbG93cyB5b3UgdG8gd3JpdGUgTWFwUmVkdWNlIGNvZGUgZW50aXJlbHkgaW4gUiBhbmQgcnVuIGl0IGFnYWluc3QgZGF0YXNldHMgb24gSGFkb29wLiBNYXBSZWR1Y2UgaXMgbm90IGFsd2F5cyB0aGUgbW9zdCBzdHJhaWdodGZvcndhcmQgd2F5IHRvIHRoaW5rIGFib3V0IHByb2Nlc3NpbmcgZGF0YSwgc28gYSBjb21wYW5pb24gcGFja2FnZSwgImRhdGFkciIgd2FzIGNyZWF0ZWQgYXMgYSBmcm9udCBlbmQgZm9yIHNwZWNpZnlpbmcgRCZSIHRhc2tzIHRoYXQgYXJlIHRyYW5zbGF0ZWQgaW50byBNYXBSZWR1Y2UgY29kZS4gQSBbdHV0b3JpYWwgb24gdGhpc10oaHR0cHM6Ly9naXRodWIuY29tL1F1YW50aWEtQW5hbHl0aWNzL1N0cmF0YS1CaWctRGF0YS13LVIpIHdhcyBnaXZlbiBsYXN0IHllYXIgYXQgU3RyYXRhIEhhZG9vcCBXb3JsZCBhbmQgbW9yZSBhYm91dCB0aGVzZSBwYWNrYWdlcyBjYW4gYmUgZm91bmQgYXQgW2RlbHRhcmhvLm9yZ10oaHR0cDovL2RlbHRhcmhvLm9yZykuCgojIyMgQWR2YW50YWdlcyBvZiBSSElQRSArIGRhdGFkcgoKLSBXaXRoIGRhdGFkciBhbmQgUkhJUEUsIHlvdSBjYW4gZXhlY3V0ZSBhcmJpdHJhcnkgUiBjb2RlIGFnYWluc3QgYXJiaXRyYXJ5IFIgb2JqZWN0cyBhdCBzY2FsZSBvbiBhIEhhZG9vcCBjbHVzdGVyIC0gYSBmbGV4aWJpbGl0eSB0aGF0IGlzIGFic29sdXRlbHkgY3JpdGljYWwgaW4gYWxtb3N0IGFueSByZWFsLXdvcmxkIGFuYWx5c2lzLCBhbmQgb25lIHRoYXQgdHJ1bHkgbGV2ZXJhZ2VzIHRoZSBpbW1lbnNlIGxpYnJhcnkgb2YgYW5hbHl0aWNhbCBtZXRob2RzIGF2YWlsYWJsZSBpbiBSLiBXaXRoIGRwbHlyIGFuZCBzcGFya2x5ciwgeW91IGFyZSBjb25zdHJhaW5lZCB0byB0aGUgc2V0IG9mIG9wZXJhdGlvbnMgdGhhdCBjYW4gYmUgdHJhbnNsYXRlZCBpbnRvIFNRTCBhbmQgeW91IGFyZSBhbHdheXMgZGVhbGluZyB3aXRoIHRhYnVsYXIgZGF0YS4KLSBEaXZpc2lvbnMgb2YgZGF0YSBhcmUgKipwZXJzaXN0ZW50KiouIEl0IHRha2VzIGEgbG90IG9mIGVmZm9ydCB0byBzaHVmZmxlIGxhcmdlIGFtb3VudHMgb2YgZGF0YSBhcm91bmQsIGFuZCB1c3VhbGx5IGlmIHlvdSBoYXZlIGEgdXNlZnVsIGRpdmlzaW9uIGluIG1pbmQsIGl0J3Mgc29tZXRoaW5nIHlvdSB3YW50IHRvIGNyZWF0ZSBvbmNlIGFuZCByZS11c2UgbWFueSB0aW1lcy4gQWxzbywgYWZ0ZXIgeW91J3ZlIGNyZWF0ZWQgYSBkaXZpc2lvbiwgeW91IG9mdGVuIHdhbnQgdG8gdHJhbnNmb3JtIGl0IHRvIHNvbWV0aGluZyB0aGF0IGlzbid0IHRhYnVsYXIgYW55bW9yZS4gWW91IGNhbiBkbyB0aGlzIG5hdHVyYWxseSB3aXRoIGRhdGFkciBhbmQgc3Vic2VxdWVudCBwZXItZ3JvdXAgb3BlcmF0aW9ucyBhcmUgdmVyeSBuYXR1cmFsIGFuZCBmYXN0LiBXaXRoIHNwYXJrbHlyLCAidmlydHVhbCIgZGl2aXNpb25zIGFyZSBhdHRhaW5lZCB1c2luZyB0aGUgYGdyb3VwX2J5YCB2ZXJiIGFuZCBtdXN0IGJlIHNwZWNpZmllZCBmb3IgZXZlcnkgb3BlcmF0aW9uLCBhbmQgeW91ciBkYXRhIGlzIGFsd2F5cyB0YWJ1bGFyIHRocm91Z2hvdXQgdGhlIHByb2Nlc3MuCgojIyMgQWR2YW50YWdlcyBvZiBzcGFya2x5ciArIGRwbHlyCgotIEJhc2VkIG9uIGEgdmVyeSBwb3B1bGFyLCB1YmlxdWl0b3VzLCBhbmQgZXhwcmVzc2l2ZSBpbnRlcmZhY2UsIGRwbHlyLgotIEdyZWF0IGRldmVsb3BtZW50IGNvbW11bml0eSBzdXBwb3J0IChSU3R1ZGlvKS4KLSBNb21lbnR1bS4KCiMjIyBUaGUgRnV0dXJlIG9mIEQmUiBBcmNoaXRlY3R1cmVzCgpXaGlsZSB0aGVyZSBhcmUgc29tZSBjcml0aWNhbCBiaWcgZGF0YSBuZWVkcyB0aGF0IGRhdGFkci9SSElQRS9IYWRvb3AgYWRkcmVzc2VzLCBnaXZlbiB0aGUgbW9tZW50dW0gb2YgYm90aCBTcGFyayBhbmQgdGhlIFtUaWR5dmVyc2VdKGh0dHA6Ly90aWR5dmVyc2Uub3JnLyksIHdoaWNoIGluY2x1ZGVzIGRwbHlyLCBhbmQgdGhlIGVtZXJnZW5jZSBvZiB1c2luZyBbbGlzdC1jb2x1bW5zXShodHRwOi8vcjRkcy5oYWQuY28ubnovbWFueS1tb2RlbHMuaHRtbCNsaXN0LWNvbHVtbnMtMSkgaW4gZGF0YSBmcmFtZXMgdG8gaGFuZGxlIGFyYml0cmFyeSBkYXRhLCB3ZSBlbnZpc2lvbiBhIGZ1dHVyZSBEJlIgZW52aXJvbm1lbnQgYmFzZWQgb24gdGhlc2UgdGVjaG5vbG9naWVzIHRoYXQgY2FuIGdpdmUgdXMgYXJiaXRyYXJ5IFIgZXhlY3V0aW9uIGFuZCBhcmJpdHJhcnkgZGF0YSBzdHJ1Y3R1cmVzIGF0IHNjYWxlLgoKSW4gdGhpcyB0dXRvcmlhbCwgd2UgdXNlIHNwYXJrbHlyIGFuZCBhcmUgbGltaXRlZCB0byB1c2luZyBpdCB0byBzdW1tYXJpemUgYSBsYXJnZXIgZGF0YXNldCBmb3IgdGhlIHB1cnBvc2Ugb2YgY3JlYXRpbmcgdmlzdWFsaXphdGlvbnMsIHdoaWNoIGdldHMgdXMgcHJldHR5IGZhci4KCiMjIEdldHRpbmcgU3RhcnRlZAoKTGV0J3Mgbm93IG1vdmUgb24gdG8gc29tZSBoYW5kcy1vbiBleGFtcGxlcy4KCiMjIyBTdGFydGluZyBhbmQgQ29ubmVjdGluZyB0byBTcGFyayBDbHVzdGVyCgpJdHMgdGltZSB0byBzdGFydCBhIFNwYXJrIGNsdXN0ZXIgYW5kIGNyZWF0ZSBhIGNvbm5lY3Rpb24gd2l0aCBgc3BhcmtseXJgLiBJbiB0aGlzIGNhc2UsIHlvdSB3aWxsIHN0YXJ0IFNwYXJrIG9uIHlvdXIgbG9jYWwgbWFjaGluZS4gU3Bhcmsgc2hvdWxkIGJlIGluc3RhbGxlZCBvbiB5b3VyIHN5c3RlbSBhbHJlYWR5IGZyb20gZm9sbG93aW5nIHRoZSBbaW5zdGFsbGF0aW9uIGluc3RydWN0aW9uc10oaHR0cHM6Ly9naXRodWIuY29tL2hhZmVuL3N0cmF0YTIwMTcpLiBGb3IgbGFyZ2Ugc2NhbGUgYXBwbGljYXRpb25zLCBTcGFyayBpcyBydW4gb24gYSByZW1vdGUgY2x1c3Rlci4KClRoZSBjb25uZWN0aW9uIG9iamVjdCwgY2FsbGVkIGBzY2AgaW4gdGhpcyBjYXNlLCBtYW5hZ2VzIHRoZSBjb25uZWN0aW9uIGJldHdlZW4geW91ciBsb2NhbCBSIHNlc3Npb24gYW5kIFNwYXJrLiBZb3Ugd2lsbCB1c2UgcmVmZXJlbmNlcyB0byB0aGUgU3BhcmsgY29ubmVjdGlvbiB3aGVuZXZlciB5b3Ugc2VuZCBkYXRhIGFuZCBjb21tYW5kcyB0byBTcGFyayBvciByZWNlaXZlIHJlc3VsdHMgYmFjay4KCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzcGFya2x5cikKbGlicmFyeSh0cmVsbGlzY29wZWpzKQpsaWJyYXJ5KGZvcmNhdHMpCgphaXJsaW5lcyA8LSByZWFkcjo6cmVhZF9jc3YoZmlsZS5wYXRoKCdkYXRhJywgJ2FpcmxpbmVzLmNzdicpKQoKc2MgPC0gc3BhcmtfY29ubmVjdChtYXN0ZXIgPSAibG9jYWwiKQpgYGAKCiMjIyBMb2FkaW5nIERhdGEgaW50byBTcGFyawoKTm93IHRoYXQgeW91IGhhdmUgYSBTcGFyayBpbnN0YW5jZSBydW5uaW5nLCB5b3UgY2FuIGxvYWQgdGhlIGRhdGEgZnJvbSB0aGUgLmNzdiBmaWxlIGluIHlvdXIgbG9jYWwgZGlyZWN0b3J5IGludG8gU3BhcmsuIElmIHlvdSBhcmUgd29ya2luZyB3aXRoIGxhcmdlIHNjYWxlIGRhdGEsIHlvdSB3aWxsIG5lZWQgdG8gdXNlIHRoZSBtb3JlIHNjYWxhYmxlIGRhdGEgbG9hZGluZyBjYXBhYmlsaXRpZXMgb2YgU3BhcmsgYW5kIHdpbGwgbm90IGxvYWQgdGhlIGRhdGEgZnJvbSBhIC5jc3YgZmlsZS4KCllvdSAqKmRvIG5vdCBsb2FkIHlvdXIgbGFyZ2UgZGF0YXNldCBpbnRvIHlvdXIgbG9jYWwgUiBzZXNzaW9uLioqIFRoZSBwb2ludCBvZiB0aGUgRCZSIHBhcmFkaWdtIGlzIHRvICoqdXNlIGEgbWFzc2l2ZWx5IHNjYWxhYmxlIGJhY2sgZW5kKiogZm9yIHRoZSBoZWF2eSBsaWZ0aW5nLiBPbmx5IHRoZSAqKnJlY29tYmluZWQgcmVzdWx0cyBhcmUgY29sbGVjdGVkIGludG8gdGhlIGxvY2FsIFIgc2Vzc2lvbioqLiBJbiB0aGlzIGNhc2UsIHdlIGFyZSB1c2luZyBTcGFyayBmb3Igb3VyIGJhY2stZW5kLiBPdGhlciBjaG9pY2VzLCBzdWNoIGFzIEhhZG9vcCwgd291bGQgYmUgc3VpdGFibGUgYXMgd2VsbC4KCk5vdGljZSwgdGhhdCB0aGUgZmlyc3QgYXJndW1lbnQgb2YgdGhlIGNvbW1hbmQgYmVsb3cgaXMgYHNjYCwgYSByZWZlcmVuY2UgdG8gdGhlIFNwYXJrIGNvbm5lY3Rpb24geW91IGhhdmUgc3RhcnRlZC4gVGhlIG5hbWUgYXNzaWduZWQsIGBmbGlnaHRzX3RibGAgaXMgYSByZWZlcmVuY2UgeW91IHdpbGwgdXNlIGluIFIgdG8gYWNjZXNzIHRoZSBkYXRhIGluIFNwYXJrLiBFeGVjdXRlIHRoaXMgY29kZSB0byBsb2FkIHRoZSBkYXRhIGludG8geW91ciBTcGFyayBzZXNzaW9uLgoKYGBge3J9CmZsaWdodHNfdGJsIDwtIHNwYXJrX3JlYWRfY3N2KHNjLCAiZmxpZ2h0c19jc3YiLCAiZGF0YS9mbGlnaHRzMjAxNi5jc3YuZ3oiKQpgYGAKClRoaXMgbWF5IHRha2UgYSBmZXcgbWludXRlcyB0byBydW4uIEFzIG5vdGVkLCBgZmxpZ2h0c190YmxgIGlzIGEgcmVmZXJlbmNlIHRvIHlvdXIgZGF0YSBpbiBTcGFyaywgYnV0IHdlIGNhbiB0cmVhdCBpdCBpbiBtYW55IHdheXMgbGlrZSBhIGRhdGEgZnJhbWUgaW4gUi4KClRvIGNoZWNrIHRoYXQgdGhlIGRhdGEgd2FzIHJlYWQgcHJvcGVybHksIHdlIGNhbiBwcmludCB0aGUgb2JqZWN0LiBUaGlzIHB1bGxzIGEgc3Vic2V0IG9mIHRoZSBkYXRhIGludG8gb3VyIGxvY2FsIFIgc2Vzc2lvbiBmb3Igdmlld2luZy4KCmBgYHtyfQpmbGlnaHRzX3RibApgYGAKClRoaXMgZ2l2ZXMgdXMgYSBmZWVsIGZvciB3aGF0IHZhcmlhYmxlcyBhcmUgaW4gdGhlIGRhdGEgYW5kIGhvdyBtYW55IHJlY29yZHMgdGhlcmUgYXJlLiBOb3RpY2UgYWxzbywgdGhhdCB3ZSBoYXZlIGFib3V0IDUuNiBtaWxsaW9uIHJvd3Mgb2YgZGF0YS4KCiMjIEEgRCZSIEV4YW1wbGU6IEV4cGxvcmluZyBEYXRhIFVzaW5nIGRwbHlyCgpOb3cgdGhhdCB0aGUgZGF0YSBoYXMgYmVlbiBsb2FkZWQgaW50byBTcGFyayB3ZSBjYW4gc3RhcnQgb3VyIGZpcnN0ICoqZGl2aWRlIGFuZCByZWNvbWJpbmUgKEQmUikqKiBleGFtcGxlLiBUaGUgc3RlcHMgb2YgdGhpcyBEJlIgZXhhbXBsZSBhcmU6CgotIFRoZSBkYXRhIGFyZSBkaXZpZGVkIGJ5IHRoZSBhaXJsaW5lIGNvZGUgdXNpbmcgYSBgZ3JvdXBfYnlgIG9wZXJhdGlvbi4gSW4gdGhpcyBjYXNlLCB0aGVyZSBhcmUgMjAgZ3JvdXBzLgotIFRoZSBtZWFuIGZvciBlYWNoIGdyb3VwIGlzIGNvbXB1dGVkIHVzaW5nIHRoZSBkcGx5ciBgc3VtbWFyaXplYCB2ZXJiLiBUaGVzZSBjYWxjdWxhdGlvbnMgYXJlIGluZGVwZW5kZW50IG9mIGVhY2ggb3RoZXIgaW4gYWxsIHJlc3BlY3RzLiBUaGV5IGNhbiBiZSBkb25lIGluIHBhcmFsbGVsIGV2ZW4gb24gZGlmZmVyZW50IG5vZGVzIG9mIGEgY2x1c3Rlci4gQW55IG90aGVyIHN1bW1hcnkgc3RhdGlzdGljcyBjYW4gYmUgY29tcHV0ZWQgaW4gcGFyYWxsZWwgYXMgd2VsbC4KLSBUaGUgcmVzdWx0cyBhcmUgbm93IGp1c3Qgb25lIG1lYW4gdmFsdWUgZm9yIGVhY2ggYWlybGluZS4gVGhleSBhcmUgZWFzaWx5IHJlY29tYmluZWQgaW50byBhIHZlY3RvciBhbmQgdGhlbiBzb3J0ZWQgdXNpbmcgdGhlIGBhcnJhbmdlYCB2ZXJiLgoKSWRlYWxseSB3ZSB3b3VsZCBoYXZlIGxpa2VkIHRvIGNvbXB1dGUgcXVhcnRpbGVzIGFuZCB0aGUgbWVkaWFuIGJ1dCBzcGFya2x5ciBkb2Vzbid0IHN1cHBvcnQgdGhlc2UgY2FsY3VsYXRpb25zIGFzIHBhcnQgb2YgYSBkcGx5ciBgZ3JvdXBfYnkoKWAgb3BlcmF0aW9uLgoKVGhlIGNvZGUgYmVsb3csIGFwcGxpZXMgYSBjaGFpbiBvZiBkcGx5ciAqKnZlcmJzKiogdG8gdGhlIGBmbGlnaHRzX3RibGAgZGF0YSBmcmFtZS4gVGhlc2Ugb3BlcmF0aW9ucyBhcmUgcGVyZm9ybWVkIGluIFNwYXJrIGFuZCB0aGUgcmVzdWx0cyB0cmFuc2ZlcmVkIHRvIHlvdXIgbG9jYWwgUiBzZXNzaW9uIHVzaW5nIHRoZSBgY29sbGVjdGAgdmVyYi4gRXhlY3V0ZSB0aGlzIGNvZGUgYW5kIGV4YW1pbmUgdGhlIHJlc3VsdC4KCmBgYHtyfQpjcl9hcnJfZGVsYXkgPC0gZmxpZ2h0c190YmwgJT4lCiAgZ3JvdXBfYnkoY2FycmllcikgJT4lCiAgc3VtbWFyaXNlKAogICAgbWVhbl9kZWxheSA9IG1lYW4oYXJyX2RlbGF5KSwKICAgIG4gPSBuKCkpICU+JQogIGFycmFuZ2UobWVhbl9kZWxheSkgJT4lCiAgY29sbGVjdCgpCgpjcl9hcnJfZGVsYXkgIyBQcmludCB0aGUgcmVzdWx0cwpgYGAKClRoZSBEJlIgcHJvY2VzcyBoYXMgcmVkdWNlZCA1LjYgbWlsbGlvbiByb3dzIG9mIHJhdyBkYXRhIHRvIDEyIHJvd3Mgb2Ygc3VtbWFyeSBzdGF0aXN0aWNzLgoKRm9yIHRoaXMgZXhhbXBsZSwgd2UgdXNlZCB0aGUgZHBseXIgcGFja2FnZSB3aXRoIHNwYXJrbHlyLiBUaGUgUiBkcGx5ciBwYWNrYWdlLCBjb21iaW5lZCB3aXRoIHNwYXJrbHlyLCBpcyB1c2VkIHRvIHNjcmlwdCBjb21wbGV4IGRhdGEgbXVuZ2luZyBhbmQgYW5hbHlzaXMgb3BlcmF0aW9ucyBpbiBTcGFyay4KCi0gZHBseXIgcGVyZm9ybXMgY29tbW9uIGRhdGEgbWFuaXB1bGF0aW9uIG9yIGRhdGEgbXVuZ2luZyBvcGVyYXRpb25zIHVzaW5nIGEgc2VyaWVzIG9mIG9wZXJhdG9ycyBjYWxsIGB2ZXJic2AuCiAgLSBDb21wbGV4IGRhdGEgbXVuZ2luZyBvcGVyYXRpb25zIGFyZSBjb25zdHJ1Y3RlZCBieSAqKmNoYWluaW5nKiogdGhlIHNpbXBsZSB2ZXJicy4gVGhlIG91dHB1dCBvZiBvbmUgdmVyYiBpcyBjb25uZWN0ZWQgdG8gdGhlIGlucHV0IG9mIHRoZSBuZXh0IHVzaW5nIHRoZSAqKmNoYWluaW5nIG9wZXJhdG9yKiosIGAlPiVgCiAgLSBJZiB5b3UgYXJlIG5vdCBmYW1pbGlhciB3aXRoIGRwbHlyIHRoZXJlIGlzIGEgZ29vZCBbdHV0b3JpYWwgdmlnbmV0dGVdKGh0dHBzOi8vY3Jhbi5yc3R1ZGlvLmNvbS93ZWIvcGFja2FnZXMvZHBseXIvdmlnbmV0dGVzL2ludHJvZHVjdGlvbi5odG1sKSBvbiBDUkFOLgotIHNwYXJrbHlyIHVzZXMgYSBzdWJzZXQgb2YgdGhlIGRwbHlyIHZlcmJzIHRvIHNjcmlwdCBvcGVyYXRpb24gaW4gU3BhcmsuCiAgLSBWZXJiIGNoYWlucyBhcmUgZGVmaW5lZCBpbiBSLgogIC0gVGhlIHBpcGVsaW5lIGRlZmluZWQgYnkgdGhlIHZlcmIgY2hhaW4gaXMgZXhlY3V0ZWQgaW4gU3BhcmsuCiAgLSBSZXN1bHRzIGFyZSBwdWxsZWQgaW50byB0aGUgbG9jYWwgUiBzZXNzaW9uIHVzaW5nIHRoZSBgY29sbGVjdGAgdmVyYi4KICAtIFRoZXJlIGFyZSBjb21wcmVoZW5zaXZlIFt0dXRvcmlhbHMgYW4gZG9jdW1lbnRhdGlvbl0oaHR0cDovL3NwYXJrLnJzdHVkaW8uY29tLykgZm9yIHNwYXJrbHlyLgoKIyMgQ3JlYXRpbmcgYSBGaXJzdCBQbG90CgpVbHRpbWF0ZWx5IGluIHRoaXMgdHV0b3JpYWwgd2Ugd2FudCB0byBkZW1vbnN0cmF0ZSBzb21lIHBvd2VyZnVsIHZpc3VhbGl6YXRpb24gdG9vbHMgZm9yIGludGVyYWN0aXZlbHkgZXhwbG9yaW5nIGxhcmdlIGRhdGFzZXRzIGluIGRldGFpbCwgYnV0IHdpdGggYSBuZXcgZGF0YXNldCwgaXQgaXMgb2Z0ZW4gYmVzdCB0byBmaXJzdCBsb29rIGF0IHNvbWUgaGlnaC1sZXZlbCBzdW1tYXJ5IHZpc3VhbGl6YXRpb25zIHRoYXQgaGVscCBndWlkZSB1cyB0b3dhcmQgYmVoYXZpb3JzIHdlIG1pZ2h0IHdhbnQgdG8gaW5zcGVjdCBpbiBtb3JlIGRldGFpbC4KCkdpdmVuIHRoZSBzdW1tYXJ5IHN0YXRpc3RpY3Mgb3V0cHV0IGZyb20gb3VyIG9wZXJhdGlvbiBhYm92ZSwgY2FsY3VsYXRpbmcgdGhlIG1lYW4gZGVsYXkgYnkgY2Fycmllciwgd2Ugd2lsbCBjcmVhdGUgc29tZSBwbG90cyB0byBmdXJ0aGVyIGV4cGxvcmUgdGhlIHJlbGF0aW9uc2hpcHMgaW4gdGhlc2UgcmVzdWx0cy4KCkFzIGEgZmlyc3Qgc3RlcCwgd2UgbmVlZCB0byBqb2luIHNvbWUgaHVtYW4gcmVhZGFibGUgbmFtZXMgdG8gdGhlIHN1bW1hcnkgc3RhdGlzdGljcyBkYXRhIGZyYW1lLgoKYGBge3J9CiMgbWVyZ2UgdGhlIGFpcmxpbmUgaW5mbyBzbyB3ZSBrbm93IHdobyB0aGUgY2FycmllcnMgYXJlCmNyX2Fycl9kZWxheSA8LSBsZWZ0X2pvaW4oY3JfYXJyX2RlbGF5LCBhaXJsaW5lcykKCmNyX2Fycl9kZWxheQpgYGAKCk5vdyB0aGF0IHRoZSBkYXRhc2V0IGlzIHByZXBhcmVkLCBsZXQncyBtYWtlIHNvbWUgc2ltcGxlIHBsb3RzIHVzaW5nIHRoZSBgZ2dwbG90MmAgcGFja2FnZS4gVGhlIGNvZGUgaW4gY2VsbCBiZWxvdyB1c2VzIGdncGxvdCB0byBleHBsb3JlIHRoZSBtZWFuIGRlbGF5IGJ5IGFpcmxpbmUgbmFtZSBhbmQgdGhlIG51bWJlciBvZiBmbGlnaHRzIGJ5IGFpcmxpbmUuCgoKYGBge3J9CmdncGxvdChjcl9hcnJfZGVsYXksIGFlcyhmY3RfcmVvcmRlcihuYW1lLCBtZWFuX2RlbGF5KSwgbWVhbl9kZWxheSkpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsKICB4bGFiKE5VTEwpICsKICB5bGFiKCJNZWFuIEFycml2YWwgRGVsYXkgKG1pbnV0ZXMpIikKYGBgCgoKKioqCioqTm90ZToqKiBJbiB0aGlzIHR1dG9yaWFsIHdlIGFzc3VtZSB5b3UgaGF2ZSBzb21lIGV4cG9zdXJlIHRvIHRoZSBnZ3Bsb3QyIHBhY2thZ2UuCgotIFRoZWBnZ3Bsb3RgZnVuY3Rpb24gZGVmaW5lcyBhIGRhdGEgZnJhbWUgdG8gb3BlcmF0ZSBvbi4KLSBUaGUgYGFlc2AgZnVuY3Rpb24gZGVmaW5lcyB0aGUgY29sdW1ucyB0byB1c2UgZm9yIHRoZSB2YXJpb3VzIGRpbWVuc2lvbnMgb2YgdGhlIHBsb3QsIGUuZy4geCwgeWNvbG9yLCBzaGFwZS4KLSBUaGUgcGxvdCB0eXBlIGF0dHJpYnV0ZSBpcyBkZWZpbmVkIGJ5IG9uZSBvciBtb3JlIGdlb21ldHJ5IGZ1bmN0aW9ucywgZS5nLiBnZW9tX3BvaW50LCBnZW9tX2xpbmUsIGdlb21fYm94cGxvdC4KLSBPdGhlciBwbG90IGF0dHJpYnV0ZXMgYXJlIGRlZmluZWQgYnkgdGhlIGFwcHJvcHJpYXRlIGZ1bmN0aW9uLCBlLmcuIHhsYWIsIGdndGl0bGUsIHRoZW1lLgotIEFsbCBvZiB0aGUgZnVuY3Rpb25zIHJlcXVpcmVkIHRvIGNyZWF0ZSBhIGNvbXBsZXRlIHBsb3QgYXJlICoqY2hhaW5lZCoqIHRvZ2V0aGVyIHdpdGggdGhlICoqY2hhaW5pbmcgb3BlcmF0b3IqKiwgYCtgLgotIFRoZXJlIGlzIGEgW2NvbXByZWhlbnNpdmUgZ2dwbG90MiBkb2N1bWVudGF0aW9uIGluZGV4XShodHRwOi8vZG9jcy5nZ3Bsb3QyLm9yZy9jdXJyZW50LykgZm9yIGFsbCBmdW5jdGlvbnMgYXZhaWxhYmxlLgoqKioKCioqKgoqKllvdXIgVHVybjoqKiBXZSBoYXZlIGxvb2tlZCBhdCB0aGUgbWVhbiBkZWxheSBvZiBmbGlnaHRzIGJ5IGFpcmxpbmUuIEJ1dCBob3cgZG9lcyB0aGUgKiptZWFuIGRpc3RhbmNlKiogb2YgdGhlIGZsaWdodCBjaGFuZ2UgYnkgYWlybGluZT8gSXMgdGhlcmUgYSByZWxhdGlvbnNoaXAgYmV0d2VlbiBhIGNhcnJpZXIncyBtZWFuIGRpc3RhbmNlIGFuZCBtZWFuIGRlbGF5PyBJbiB0aGUgc3BhY2UgYmVsb3cgY3JlYXRlIGFuZCBleGVjdXRlIGNvZGUgdG8gZG8gdGhlIGZvbGxvd2luZzoKLSBVc2Ugc3BhcmtseXIgdG8gY29tcHV0ZSBhIG5ldyBgY3JfYXJyX2RlbGF5YCBkYXRhIGZyYW1lLCBpbmNsdWRpbmcgYWxsIHRoZSBzYW1lIGNvbHVtbnMgYXMgYmVmb3JlIGJ1dCBhbHNvIGEgbmV3IGBtZWFuX2Rpc3RhbmNlYCBjb2x1bW4uCi0gSm9pbiB0aGUgYWlybGluZSBuYW1lcyB0byB0aGUgYGNyX2Fycl9kZWxheWAgZGF0YSBmcmFtZS4KLSBVc2UgZ2dwbG90MiB0byBwbG90IHRoZSBtZWFuIGRpc3RhbmNlIGJ5IGFpcmxpbmUuCgpgYGB7cn0KY2F0KCdZb3VyIGNvZGUgZ29lcyBoZXJlJykKYGBgCioqKgoKQXMgZGlzY3Vzc2VkIGJlZm9yZSwgaXQgaXMgaW1wb3J0YW50IHRvIGludmVzdGlnYXRlIG11bHRpcGxlIHZpZXdzIG9mIGEgZGF0YXNldC4gTm93LCB0aGUgcXVlc3Rpb24gaXMsIHdoYXQgaXMgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG51bWJlciBvZiBmbGlnaHRzIGFuZCBtZWFuIGRlbGF5LCBhbmQgbWVhbiBkZWxheSBhbmQgbWVhbiBkaXN0YW5jZSBvZiB0aGUgZmxpZ2h0cy4gVGhlIGNvZGUgaW4gdGhlIGNlbGxzIGJlbG93IGRpc3BsYXlzIHRoZXNlIHBsb3RzLgoKYGBge3J9CmdncGxvdChjcl9hcnJfZGVsYXksIGFlcyhtZWFuX2RlbGF5LCBuKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKwogIHhsYWIoJ01lYW4gZGVsYXkgaW4gbWludXRlcycpICsKICB5bGFiKCJOdW1iZXIgb2YgZmxpZ2h0cyBieSBhaXJsaW5lIikKYGBgCgpgYGB7cn0KZ2dwbG90KGNyX2Fycl9kZWxheSwgYWVzKG1lYW5fZGVsYXksIG1lYW5fZGlzdGFuY2UpKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArCiAgeGxhYignTWVhbiBkZWxheSBpbiBtaW51dGVzJykgKwogIHlsYWIoIk1lYW4gZGlzdGFuY2UgaW4gbWlsZXMiKQpgYGAKCgojIyBQbG90dGluZyBXaXRoIGEgTGl0dGxlIE1vcmUgRGV0YWlsCgpJbiB0aGUgcHJldmlvdXMgZXhhbXBsZSB3ZSB3b3JrZWQgd2l0aCBhIGZhaXJseSBzaW1wbGUgc2V0IG9mIHN1bW1hcnkgc3RhdGlzdGljcy4gVGhlIG1lYW4gZGVsYXksIG51bWJlciBvZiBmbGlnaHRzIGFuZCBtZWFuIGRpc3RhbmNlIG9mIGZsaWdodHMgYWxsIGdyb3VwZWQgYnkgYSBzaW5nbGUgZmFjdG9yLCBhaXJsaW5lLiBUaGVzZSByZWxhdGlvbnNoaXBzIGdpdmUgdXMgc29tZSBpbnRlcmVzdGluZyBpbnNpZ2h0IGludG8gdGhlc2UgZGF0YSwgYnV0IHN1cmVseSwgd2UgY2FuIGxlYXJuIG1vcmUgYWJvdXQgdGhpcyBkYXRhc2V0LgoKTGV0J3MgdHJ5IGFub3RoZXIgRCZSIGV4YW1wbGUuIEluIHRoaXMgY2FzZSB3ZSB3aWxsIGRpdmlkZSB0aGUgZGF0YSBib3RoIGJ5IGFpcmxpbmUgYW5kIG1vbnRoLiBUaGUgYmFzaWMgRCZSIHBpcGVsaW5lIGlzIHNpbWlsYXIgdG8gdGhlIG9uZSB3ZSB1c2VkIGJlZm9yZSwgYnV0IHRoZSByZXN1bHRzIGFyZSBtb3JlIGdyYW51bGFyLiBUaGUgY29kZSBpbiB0aGUgY2VsbCBiZWxvdyBwZXJmb3JtcyB0aGUgZm9sbG93aW5nIGRpdmlkZSBhbmQgcmVjb21iaW5lIG9wZXJhdGlvbnM6CgotIFRoZSBkYXRhIGlzIGRpdmlkZWQgYnkgZWFjaCBjYXJyaWVyIGFuZCBtb250aCBwYWlyLgotIFN1bW1hcnkgc3RhdGlzdGljcyBhcmUgY29tcHV0ZWQgZm9yIGVhY2ggZGl2aXNpb24gb2YgdGhlIGRhdGEuCi0gVGhlIHJlY29tYmluZWQgcmVzdWx0cyBhcmUgY29sbGVjdGVkIHRvIHRoZSBsb2NhbCBSIHNlc3Npb24uCi0gVGhlIGFpcmxpbmVzIG5hbWVzIGFyZSBqb2luZWQgYW5kIHRoZSBhaXJsaW5lIGNvZGVzIGFyZSBzdWJzdGl0dXRlZCBmb3IgdGhlIG1pc3NpbmcgdmFsdWVzLgoKYGBge1J9CmNyX21uX2Fycl9kZWxheSA8LSBmbGlnaHRzX3RibCAlPiUKICBncm91cF9ieShjYXJyaWVyLCBtb250aCkgJT4lCiAgc3VtbWFyaXNlKAogICAgbWVhbl9kZWxheSA9IG1lYW4oYXJyX2RlbGF5KSwKICAgIG1lYW5fZGlzdGFuY2UgPSBtZWFuKGRpc3RhbmNlKSwKICAgIG4gPSBuKCkpICU+JQogIGNvbGxlY3QoKSAlPiUKICBsZWZ0X2pvaW4oYWlybGluZXMpICU+JQogIG11dGF0ZShtb250aCA9IGZhY3Rvcihtb250aCkpCgpjcl9tbl9hcnJfZGVsYXkKYGBgCgpXZSBub3cgaGF2ZSAxMiBtb250aHMgb2Ygc3VtbWFyaWVzIGZvciBlYWNoIG9mIHRoZSAxMiBjYXJyaWVycy4gR2l2ZW4gdGhhdCB3ZSBoYXZlIG1vcmUgdmFsdWVzIGFuZCBtb3JlIHZhcmlhYmxlcywgdGhlcmUgYXJlIG1hbnkgd2F5cyB3ZSBtaWdodCB2aXN1YWxpemUgdGhlc2Ugc3VtbWFyaWVzLiBJbiB0aGlzIGNhc2Ugd2Ugd2lsbCB1c2UgYSBwb3dlcmZ1bCBtZXRob2Qga25vdyB2YXJpb3VzbHkgYXMgYSAqKmZhY2V0IHBsb3QqKiwgKipjb25kaXRpb25lZCBwbG90KiosICoqdHJlbGxpcyBwbG90KiosIG9yIHRoZSAqKm1ldGhvZCBvZiBzbWFsbCBtdWx0aXBsZXMqKi4KCkEgZmFjZXRlZCBvciBjb25kaXRpb25lZCBwbG90IGlzIGNvbXByaXNlZCBvZiBhIHNldCBvZiBzdWItcGxvdHMgZGVmaW5lZCBieSBvbmUgb3IgbW9yZSBjb25kaXRpb25pbmcgdmFyaWFibGVzLiBUaGUgZGF0YSBmb3IgZWFjaCBzdWItcGxvdCBpcyB0aGUgcmVzdWx0IG9mIGEgcGFydGl0aW9uaW5nIGJhc2VkIG9uIHRoZSB2YWx1ZXMgb2YgdGhlIGNvbmRpdGlvbmluZyB2YXJpYWJsZS4gVGhpcyBjb25kaXRpb25pbmcgb3BlcmF0aW9uIGlzLCBpbiBlZmZlY3QsIGEgKipncm91cC1ieSoqIG9wZXJhdGlvbi4gVGhpcyBhcHByb2FjaCBhbGxvd3MgKipzbWFsbCBtdWx0aXBsZXMqKiBvZiBhIGxhcmdlIGNvbXBsZXggZGF0YXNldCB0byBiZSB2aWV3ZWQgaW4gYSBzeXN0ZW1hdGljIGFuZCB1bmRlcnN0YW5kYWJsZSBtYW5uZXIuCgpJbiBlZmZlY3QsIHRoZSBtZXRob2QgKipleHRlbmRzIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyBwcm9qZWN0ZWQgb250byBhIDJkIGNvbXB1dGVyIGRpc3BsYXkqKi4gVGhpcyBwcm9wZXJ0eSBtYWtlcyBjb25kaXRpb25lZCBwbG90dGluZyBhbiBpZGVhIHRvb2wgZm9yIGNvbXBsZXggZGF0YXNldHMgd2l0aCBlaXRoZXIgbWFueSB2YXJpYWJsZXMgb3IgcmVjb3Jkcy4KClRoZSBpZGVhIG9mIGEgZmFjZXQgcGxvdCBoYXMgYSBsb25nIGhpc3RvcnkuIEFuIGVhcmx5IGV4YW1wbGUgb2YgdXNpbmcgc21hbGwgbXVsdGlwbGVzIHdhcyB1c2VkIHRvIGRpc3BsYXkgc29tZSByZXN1bHRzIGZyb20gdGhlIDE4NzAgVVMgY2Vuc3VzLiBUaGUgcGxvdCBiZWxvdyBjb21iaW5lcyBzbWFsbCBtdWx0aXBsZXMgd2l0aCBhIHRyZWVtYXAgcGxvdCB0byBzaG93IHByb3BvcnRpb25zIG9mIHRoZSBwb3B1bGF0aW9uIGluIGRpZmZlcmVudCBvY2N1cGF0aW9ucyBvciBhdHRlbmRpbmcgc2Nob29sLAoKIVtdKGltYWdlcy9zbWFsbF9tdWx0aXBsZXNfMTg3MC5qcGcpCgpUaGUgc21hbGwgbXVsdGlwbGVzIGlkZWEgd2FzIHBvcHVsYXJpemVkIGluIEVkd2FyZCBUdWZ0ZSdzIDE5ODMgYm9vay4gQmlsbCBDbGV2ZWxhbmQgYW5kIGNvbGxlYWd1ZXMgYXQgQVQmVCBCZWxsIExhYnMgY3JlYXRlZCB0aGUgVHJlbGxpcyBwbG90dGluZyBzb2Z0d2FyZSBwYWNrYWdlIHVzaW5nIHRoZSBTIGxhbmd1YWdlLiBDbGV2ZWxhbmQgY2FsbGVkIHRoaXMgbWV0aG9kIFRyZWxsaXMgRGlzcGxheS4KCiFbXShpbWFnZXMvY2xldmVsYW5kLXZpc3VhbGl6aW5nLmpwZykKClRoZSBnZ3Bsb3QyIHBhY2thZ2UgY29udGFpbnMgdGhlIGBmYWNldF9ncmlkYCBmdW5jdGlvbiB3aGljaCBpcyB1c2VkIHRvIGRlZmluZSB0aGUgZ3JpZCBvbiB3aGljaCB0aGUgc3ViLXBsb3RzIGFyZSBjcmVhdGVkLiBUaGUgZmFjZXQgZ3JpZCBmdW5jdGlvbiB1c2VzIGFuIFIgZm9ybXVsYSBvYmplY3QgdG8gZGVmaW5lIHRoZSByb3dzIGFuZCBjb2x1bW5zIHRvIHNwZWNpZnkgdGhlIGNvbmRpdGlvbmluZyB2YXJpYWJsZSB1c2VkIHRvIGRlZmluZSB0aGUgcm93cyBhbmQgY29sdW1ucy4gVGhlIGdlbmVyYWwgZm9ybSBvZiB0aGlzIGZvcm11bGEgaXM6CgokJFJvd1ZhcmlhYmxlcyBcc2ltIENvbHVtblZhcmlhYmxlcyQkCgpBIGNvbmRpdGlvbmVkIHBsb3Qgd2l0aCBhIHNpbmdsZSBjb2x1bW4sIGJ1dCBtdWx0aXBsZSByb3dzLCBpcyB0aGVyZWZvcmUgZGVmaW5lZDoKCiQkUm93VmFyaWFibGVzIFxzaW1cIC4kJAoKT3IsIGNvbmRpdGlvbmVkIHBsb3Qgd2l0aCBhIHNpbmdsZSByb3csIGJ1dCBtdWx0aXBsZSBjb2x1bW5zLCBpcyBkZWZpbmVkOgoKJCQuXCBcc2ltIENvbHVtblZhcmlhYmxlcyQkCgpZb3UgY2FuIHVzZSBtdWx0aXBsZSB2YXJpYWJsZXMgdG8gY29uZGl0aW9uIHJvd3MgYW5kIGNvbHVtbnMsIHVzaW5nIHRoZSBgK2Agc3ltYm9sIGFzIHRoZSBvcGVyYXRvcjoKCiQkUm93VmFyMSArIFJvd1ZhcjIgKyBcbGRvdHMgXHNpbSBDb2xWYXIxICsgQ29sVmFyMiArIFxsZG90cyQkCgpMaWtlIGFsbCBnb29kIHRoaW5ncyBpbiB2aXN1YWxpemF0aW9uLCB0aGVyZSBhcmUgcHJhY3RpY2FsIGxpbWl0cy4gQ3JlYXRpbmcgYSBsYXJnZSBncmlkIG9mIHN1Yi1wbG90cyB1c2luZyBtdWx0aXBsZSBjb25kaXRpb25pbmcgdmFyaWFibGVzIHF1aWNrbHkgYmVjb21lcyBjb25mdXNpbmcgdG8gbG9vayBhdCBhbmQgdW5kZXJzdGFuZC4gQmVzdCBwcmFjdGljZSBpcyB0byB1c2Ugb25lIG9yIHR3byBjb25kaXRpb25pbmcgdmFyaWFibGVzIHRvIHN0YXJ0IHdpdGggYW5kIHRoZW4gdG8gZXhwbG9yZSB0aGUgZGF0YXNldCBieSBjaGFuZ2luZyBvbmUgY29uZGl0aW9uaW5nIHZhcmlhYmxlIGF0IGEgdGltZS4KClRoZSBjb2RlIGluIHRoZSBjZWxsIGJlbG93IGNyZWF0ZXMgYSBmYWNldGVkIHBsb3Qgb2YgbW9udGhseSBhdmVyYWdlIGZsaWdodCBkZWxheSBieSBtb250aC4gVGhlIGRhdGEgaW4gdGhlc2UgcGxvdHMgaXMgZ3JvdXBlZC1ieSBvciBjb25kaXRpb25lZCBvbiBmaXJzdCB0aGUgbmFtZSBvZiB0aGUgYWlybGluZSBhbmQgdGhlbiB0aGUgbWVhbiBmbGlnaHQgZGVsYXkuCgoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KZ2dwbG90KGNyX21uX2Fycl9kZWxheSwgYWVzKG1vbnRoLCBtZWFuX2RlbGF5LCBncm91cCA9IDEpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgZmFjZXRfZ3JpZCh+IGZjdF9yZW9yZGVyKG5hbWUsIG1lYW5fZGVsYXkpKQpgYGAKClRoZXJlIGlzIG9uZSBwbG90IGZvciBlYWNoIGFpcmxpbmUsIHdpdGggdGhlIG1lYW4gZGVsYXkgc2hvd24gYnkgbW9udGguIFRoZXNlIHBsb3RzIGhhdmUgYmVlbiBzb3J0ZWQgYnkgdGhlIG1lYW4gZGVsYXkgYnkgYWlybGluZSwgc28gd2UgY2FuIGZvY3VzIG9uIHRoZSBhaXJsaW5lcyB3aXRoIHRoZSBncmVhdGVzdCBhdmVyYWdlIGRlbGF5cy4gTm90aWNlIHRoYXQgdGhlcmUgYXJlIHNpZ25pZmljYW50IGNoYW5nZXMgaW4gdGhlIG1lYW4gZGVsYXlzIGJ5IG1vbnRoIGZvciBlYWNoIGFpcmxpbmUuIEFsc28gbm90aWNlIHRoYXQgc29tZSBhaXJsaW5lcyBoYXZlIHZlcnkgbGFyZ2UganVtcHMgaW4gbWVhbiBkZWxheSBpbiB0aGUgc3VtbWVyIG1vbnRocyB3aGlsZSBpdCBpcyBub3QgYXMgcHJvbm91bmNlZCBmb3Igb3RoZXIgYWlybGluZXMuCgoqKioKKipZb3VyIFR1cm46KiogTmV4dCwgbGV0J3MgbG9vayBhdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGFpcmxpbmVzIGFuZCB0aGUgbnVtYmVyIG9mIGZsaWdodHMuCgotIEluIHRoZSBjZWxsIGJlbG93IGNyZWF0ZSBhbmQgZXhlY3V0ZSB0aGUgY29kZSB0byBkaXNwbGF5IHRoZSBudW1iZXIgb2YgZmxpZ2h0cyBwZXIgbW9udGggYnkgYWlybGluZSBzb3J0ZWQgYnkgbWVhbiBmbGlnaHQgZGVsYXkuCi0gQ3JlYXRlIHR3byBwbG90cywgb25lIG9uIGEgbG9nIHNjYWxlIGFuZCBvbmUgb24gYSBsaW5lYXIgc2NhbGUuCi0gSXMgdGhpcyByZWxhdGlvbnNoaXAgdXNlZnVsIGluIHVuZGVyc3RhbmRpbmcgdGhpcyBkYXRhc2V0PwotICoqSGludCoqLCB0aGUgZ2dwbG90MiBhdHRyaWJ1dGUgZnVuY3Rpb24gYHNjYWxlX3lfbG9nMTAoKWAgd2lsbCBjcmVhdGUgYSBwbG90IHdpdGggYSBsb2cgc2NhbGUgb24gdGhlIHZlcnRpY2FsIGF4aXMuCgpgYGB7dH0KY2F0KCdZb3VyIGNvZGUgZ29lcyBoZXJlJykKYGBgCgoqKioKCiMjIFZpc3VhbGl6aW5nIEdyb3VwcyBXaXRob3V0IEZhY2V0aW5nCgpUaGUgZmFjZXRpbmcgZXhhbXBsZXMgYWJvdmUgd2VyZSB1c2VmdWwgaW4gYWxsb3dpbmcgdXMgdG8gZXhhbWluZSBhdmVyYWdlIGRlbGF5cyB2cy4gbW9udGggYnkgY2FycmllciB3aGlsZSBhbGxvd2luZyB1cyB0byBtYWtlIHZpc3VhbCBjb21wYXJpc29ucyBhY3Jvc3MgY2FycmllcnMuIE9mdGVuIGl0IGlzIHVzZWZ1bCwgaW5zdGVhZCBvZiBmYWNldGluZywgdG8gb3ZlcmxheSB0aGUgZGF0YSBmb3IgdGhlIGRpZmZlcmVudCBncm91cHMgaW4gYSBzaW5nbGUgcGxvdCB0byBtYWtlIG1vcmUgcmVsYXRpdmUgY29tcGFyaXNvbnMgYmV0d2VlbiB0aGUgZ3JvdXBzLgoKT3ZlcmxheWluZyBkYXRhIGZyb20gMTIgYWlybGluZXMgYW5kIHRyeWluZyB0byBiZSBhYmxlIHRvIHZpc3VhbGx5IGRpc3Rpbmd1aXNoIGJldHdlZW4gYWxsIG9mIHRoZW0gaXMgZGlmZmljdWx0LCBhbmQgdGhpcyBpcyBvbmUgb2YgdGhlIHJlYXNvbnMgZmFjZXRpbmcgaXMgc3VjaCBhIGdvb2QgaWRlYSAtIGl0IGhlbHBzIGRlYWwgd2l0aCAqKm92ZXJwbG90dGluZyoqLgoKV2UgY2FuIHNhY3JpZmljZSBsb29raW5nIGF0IGFsbCB0aGUgZGF0YSBpbiBhIGZhY2V0ZWQgcGxvdCB0byBmaWx0ZXJpbmcgb3V0IHNvbWUgb2YgdGhlIGRhdGEgdG8gYmUgYWJsZSB0byBnZXQgYSBtb3JlIGNsZWFyIHBpY3R1cmUgaW4gYSBzaW5nbGUgcGxvdC4gTG9va2luZyBhdCB0aGUgbnVtYmVyIG9mIGZsaWdodHMgZm9yIGVhY2ggYWlybGluZSwgdGhlcmUgaXMgYSBwcmV0dHkgY2xlYXIgc2VwYXJhdGlvbiBiZXR3ZWVuIHRoZSBib3R0b20gNiBhbmQgdGhlIHRvcCA2LiBTaW5jZSB0aGUgdG9wIDYgYWlybGluZXMgYWNjb3VudCBmb3IgODUlIG9mIGFsbCBmbGlnaHRzLCB0aGV5IGFyZSBwcm9iYWJseSB0aGUgbW9zdCBpbnRlcmVzdGluZyBhaXJsaW5lcyB0byBsb29rIGF0LCBzbyB3ZSB3aWxsIGZpbHRlciBvdXIgZGF0YSB0byBjb21wYXJlIHRoZSB0b3AgNiBhaXJsaW5lcyBpbiBhIHNpbmdsZSBwbG90IGluIGEgbWFuYWdlYWJsZSB3YXkuIFRoZSBjb2RlIGluIHRoZSBjZWxsIGJlbG93IGRvZXMgdGhlIGZvbGxvd2luZzoKCi0gRmlsdGVyIG91dCB0aGUgc21hbGwgYWlybGluZXMgYnkgb25seSBrZWVwaW5nIGFpcmxpbmVzIHdpdGggYXQgbGVhc3QgNDAwayBmbGlnaHRzIGluIDIwMTYuCi0gVGhlIHBpcGVsaW5lIGZvciBwbG90dGluZyB0aGUgbW9udGhseSBmbGlnaHQgZGVsYXlzIGRvZXMgdGhlIGZvbGxvd2luZzoKICAtIFRoZSBhaXJsaW5lcyBhcmUgZmlsdGVyZWQgZm9yIHRoZSBvbmVzIHdpdGggdGhlIGxhcmdlIG51bWJlciBvZiBmbGlnaHRzLgogIC0gQSBwbG90IGlzIGNyZWF0ZWQgb2YgdGhlIG1lYW4gZmxpZ2h0IGRlbGF5IGJ5IG1vbnRoIGZvciB0aGUgYWlybGluZXMgd2l0aCB0aGUgbGFyZ2VzdCBudW1iZXJzIG9mIGZsaWdodHMuCgpgYGB7cn0KdG9wNiA8LSBjcl9hcnJfZGVsYXkgJT4lIGZpbHRlcihuID4gNDAwMDAwKSAlPiUgLltbImNhcnJpZXIiXV0KdG9wNgoKIyBvdmVybGF5IHRoZW0gYWxsCmNyX21uX2Fycl9kZWxheSAlPiUKICBmaWx0ZXIoY2FycmllciAlaW4lIHRvcDYpICU+JQogIGdncGxvdChhZXMobW9udGgsIG1lYW5fZGVsYXksIGNvbG9yID0gbmFtZSwgZ3JvdXAgPSBuYW1lKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9saW5lKCkKYGBgCgpUaGVyZSBhcHBlYXJzIHRvIGJlIGEgc2Vhc29uYWwgcGF0dGVybiB0byB0aGUgbWVhbiBkZWxheXMgZm9yIGFsbCBvZiB0aGUgdG9wIDYgY2FycmllcnMsIHdoaWNoIGlzIHNpbWlsYXIgZm9yIGVhY2ggYWlybGluZS4gT2YgY291cnNlLCBtb3JlIHllYXJzIG9mIGRhdGEgd291bGQgaGVscCB1cyBtb3JlIHN0cm9uZ2x5IHN1cHBvcnQgdGhpcyBjb25jbHVzaW9uLiBXZSBzZWUgdGhhdCBpbiAyMDE2LCBEZWx0YSB0eXBpY2FsbHkgaGFkIHRoZSBiZXN0IGF2ZXJhZ2Ugb24gdGltZSBwZXJmb3JtYW5jZSwgZXNwZWNpYWxseSBpbiB0aGUgZmFsbCBhbmQgZWFybHkgd2ludGVyLgoKKioqCioqWW91ciBUdXJuOioqIExldCdzIG1ha2UgdGhlIHNhbWUgcGxvdCBmb3IgYWxsIGFpcmxpbmVzLgoKLSBNb2RpZnkgdGhlIGNvZGUgZm9yIHRoZSBhYm92ZSBwbG90IHRvIGNyZWF0ZWQgYSB0aGUgcGxvdCBmYWNldGVkIGJ5IHdoZXRoZXIgdGhlIGFpcmxpbmUgaXMgaW4gdGhlIHRvcCA2IG9yIGJvdHRvbSA2LiBZb3Ugd2lsbCBwcm9kdWNlIHR3byBsaW5lIHBsb3RzLgotIElzIHRoZXJlIGEgZGlmZmVyZW5jZSBpbiBzZWFzb25hbCBwYXR0ZXJucyBvciBpbiBnZW5lcmFsIGJldHdlZW4gYWlybGluZXMgaW4gdGhlIHRvcCA2IG9yIGJvdHRvbSA2PwoKYGBge3J9CmNhdCgnWW91ciBjb2RlIGdvZXMgaGVyZScpCmBgYAoKKioqCgojIyBGdXJ0aGVyIERyaWxsIERvd24gd2l0aCBUcmVsbGlzY29wZQoKV2UgaGF2ZSBzZWVuIGFuIG92ZXJhbGwgc2Vhc29uYWwgcGF0dGVybiBmb3IgdGhlIHRvcCA2IGFpcmxpbmVzLiBOb3csIHdlIGFyZSBjdXJpb3VzIHdoZXRoZXIgdGhlcmUgaXMgbW9yZSB0byB0aGlzIHZlcnkgaGlnaC1sZXZlbCBzdW1tYXJ5LgoKUXVlc3Rpb25zOgotIEFyZSBkaWZmZXJlbnQgZmxpZ2h0IHJvdXRlcyBtb3JlIHByb25lIHRvIGRlbGF5cz8KLSBkb2VzIHZhcmlhYmlsaXR5IGFjcm9zcyBhaXJsaW5lcyBjaGFuZ2UgZm9yIGRpZmZlcmVudCByb3V0ZXM/CgpXZSBjYW4gdmlzdWFsbHkgaW52ZXN0aWdhdGUgdGhlc2UgcXVlc3Rpb25zIGJ5IGNyZWF0aW5nIHRoZSBzYW1lIHBsb3QgYXMgYWJvdmUgKG1lYW4gZGVsYXkgdnMuIG1vbnRoIHdpdGggZWFjaCBjYXJyaWVyIG92ZXJsYWlkKSBmb3IgZXZlcnkgcm91dGUuIEFzIHdlIHdpbGwgc2VlLCB0aGVyZSBhcmUgbWFueSByb3V0ZXMsIHRvbyBtYW55IHRvIGxvb2sgYXQgYWxsIGF0IG9uY2UgaW4gYSBzaW1wbGUgZ2dwbG90MiBmYWNldGVkIHBsb3QuIEZvciB0aGlzLCB3ZSB3aWxsIHR1cm4gdG8gVHJlbGxpc2NvcGUsIHdoaWNoIGFsbG93cyB1cyB0byBjcmVhdGUgbGFyZ2UgZmFjZXRlZCBkaXNwbGF5cyBhbmQgaW50ZXJhY3RpdmVseSBuYXZpZ2F0ZSB0aHJvdWdoIHRoZSBwYW5lbHMgYXMgd2UgbGVhcm4gd2hhdCBpcyBoYXBwZW5pbmcgaW4gZWFjaCBzdWJzZXQgb2YgdGhlIGRhdGEuCgpXZSBjYW4gZ2V0IHRoZSBkYXRhIGludG8gc2hhcGUgZm9yIHRoaXMgdGFzayBieSBncm91cGluZyBieSByb3V0ZSAoYG9yaWdpbmAgYW5kIGBkZXN0YCksIGBtb250aGAsIGFuZCBgbmFtZWAuIFNpbmNlIHdlIGFyZSBsb29raW5nIGF0IHRoZSBtZWFuIGRlbGF5LCBhbmQgc29tZSByb3V0ZXMgYXJlIHRyYXZlbGVkIG1vcmUgcmFyZWx5LCB3ZSB3YW50IHRvIG1ha2Ugc3VyZSB3ZSBoYXZlIGVub3VnaCBkYXRhIHRvIGNvbXB1dGUgYSBtZWFuaW5nZnVsIHN0YXRpc3RpYy4gQmVjYXVzZSBvZiB0aGlzLCB3ZSdsbCBvbmx5IGxvb2sgYXQgcm91dGVzIHRoYXQgaGF2ZSwgZm9yIGEgZ2l2ZW4gcm91dGUsIGNhcnJpZXIsIGFuZCBtb250aCwgbW9yZSB0aGFuIDUwIGZsaWdodHMuCgpXZSBuZWVkIHRvIGNyZWF0ZSBhIG5ldyBncm91cGluZyBvZiB0aGUgbGFyZ2UgZGF0YXNldCB1c2luZyBzcGFya2x5ci4gVGhlIGRwbHlyIGNvZGUgaW4gdGhlIGNlbGwgYmVsb3cgZGVmaW5lcyBhIHNwYXJrbHlyIHBpcGVsaW5lIHBlcmZvcm1pbmcgdGhlIGZvbGxvd2luZyBvcGVyYXRpb25zOgoKLSBHcm91cHMgdGhlIGRhdGEgZmlyc3QgYnkgdGhlIGZsaWdodCBvcmlnaW4sIGZsaWdodCBkZXN0aW5hdGlvbiwgY2FycmllciwgYW5kIG1vbnRoLgotIFRoZSBtZWFuIGRlbGF5IGFuZCBudW1iZXIgb2YgZmxpZ2h0cyBmb3IgZWFjaCBncm91cCBhcmUgY29tcHV0ZWQuCi0gR3JvdXBzIHdpdGggZmV3ZXIgdGhhbiAyNSBmbGlnaHRzIHBlciBtb250aCBhcmUgZmlsdGVyZWQgb3V0LgotIFJlc3VsdHMgd2l0aCBhaXJsaW5lcyBpbiB0aGUgYm90dG9tIDYgYXJlIGZpbHRlcmVkIG91dC4KLSBUaGUgcmVzdWx0cyBhcmUgY29sbGVjdGVkIGJhY2sgaW50byB5b3VyIGxvY2FsIFIgc2Vzc2lvbi4KCgpgYGB7cn0KIyBncm91cCBieSwgb3JpZ2luLCBkZXN0LCBjYXJyaWVyLCBtb250aCBhbmQgZ2V0IG1lYW4gZGVsYXkgYW5kICMgb2JzCiMgYW5kIHB1bGwgdGhpcyBiYWNrIGludG8gUgpyb3V0ZV9zdW1tID0gZmxpZ2h0c190YmwgJT4lCiAgZ3JvdXBfYnkob3JpZ2luLCBkZXN0LCBjYXJyaWVyLCBtb250aCkgJT4lCiAgc3VtbWFyaXNlKAogICAgbWVhbl9kZWxheSA9IG1lYW4oYXJyX2RlbGF5KSwKICAgIG4gPSBuKCkpICU+JQogIGZpbHRlcihuID49IDI1ICYgY2FycmllciAlaW4lIHRvcDYpICU+JQogIGNvbGxlY3QoKQoKcm91dGVfc3VtbQpgYGAKCldlIGhhdmUgZ29uZSBmcm9tIG92ZXIgNS42IG1pbGxpb24gcm93cyB0byBhYm91dCA1MGsgcm93cywgdHdvIG9yZGVycyBvZiBtYWduaXR1ZGUgcmVkdWN0aW9uIGluIHNpemUsIGFuZCBwbGVudHkgc21hbGwgdG8gbm93IGJlIHdvcmtpbmcgd2l0aCBpbiBvdXIgbG9jYWwgUiBzZXNzaW9uLgoKV2l0aCBhIGxpdHRsZSBtb3JlIHdvcmssIHdlIGNhbiBnZXQgdGhpcyBkYXRhIG1vcmUgc3VpdGFibGUgZm9yIHZpc3VhbGl6YXRpb24uCgotIFRoZSBhaXJsaW5lIG5hbWVzIGFyZSBqb2luZWQuCi0gVGhlIGFpcmxpbmUgbmFtZXMgYW5kIG1vbnRocyBhcmUgY29udmVydGVkIHRvIGEgZmFjdG9yIHZhcmlhYmxlIHdoaWNoIGlzIGhlbHBmdWwgZm9yIG1ha2luZyBwbG90cyB3aXRoIGdncGxvdDIuCgpgYGB7cn0Kcm91dGVfc3VtbTIgPC0gcm91dGVfc3VtbSAlPiUKICBsZWZ0X2pvaW4oYWlybGluZXMpICU+JQogIHJlbmFtZShjYXJyaWVyX25hbWUgPSBuYW1lKSAlPiUKICBtdXRhdGUoCiAgICBjYXJyaWVyX25hbWUgPSBmYWN0b3IoY2Fycmllcl9uYW1lKSwKICAgIG1vbnRoID0gZmFjdG9yKG1vbnRoKSkKCnJvdXRlX3N1bW0yCmBgYAoKV2UgaGF2ZSBvbmUgbW9yZSB0YXNrIHRvIGdldCB0aGUgZGF0YSBpbnRvIHRoZSBzdGF0ZSB3ZSBuZWVkIGZvciB2aXN1YWxpemF0aW9uLiBGb3IgZWFjaCByb3V0ZSwgd2Ugb25seSB3YW50IHRvIHBsb3QgZGF0YSBmb3IgYWlybGluZXMgdGhhdCByZWNvcmRlZCBmbGlnaHRzIGluIGFsbCAxMiBtb250aHMgZm9yIHRoYXQgcm91dGUuIFdlIGNhbiBnZXQgYSBsaXN0aW5nIG9mIGFsbCAiY29tcGxldGUiIGNhcnJpZXIvcm91dGUgY29tYmluYXRpb25zIHdpdGggdGhlIGZvbGxvd2luZzoKCmBgYHtyfQpjb21wbF9yb3V0ZXMgPC0gcm91dGVfc3VtbTIgJT4lCiAgZ3JvdXBfYnkob3JpZ2luLCBkZXN0LCBjYXJyaWVyKSAlPiUKICBzdW1tYXJpc2UobiA9IG4oKSkgJT4lCiAgZmlsdGVyKG4gPT0gMTIpICU+JQogIHNlbGVjdCgtbikKCmNvbXBsX3JvdXRlcwpgYGAKClRoZXJlIGFyZSBvdmVyIDMwMDAgcm91dGUgLyBjYXJyaWVyIGNvbWJpbmF0aW9ucyB3aXRoIGEgc3VtbWFyeSB2YWx1ZSBmb3IgYWxsIDEyIG1vbnRocy4gV2UgY2FuIHJlZHVjZSBvdXIgcm91dGUgc3VtbWFyeSBkYXRhIHRvIGp1c3QgdGhlc2UgY29tYmluYXRpb25zIGJ5IGpvaW5pbmcgYGNvbXBsX3JvdXRlc2Agd2l0aCBgcm91dGVfc3VtbTJgLgoKLSBXZSB1c2UgYHJpZ2h0X2pvaW4oKWAgc28gdGhhdCBvbmx5IHJvdXRlIC8gY2FycmllciBjb21iaW5hdGlvbnMgaW4gYGNvbXBsX3JvdXRlc2AgYXJlIHByZXNlcnZlZC4KLSBUaGUgam9pbiBmdW5jdGlvbiBhdXRvbWF0aWNhbGx5IGRldGVybWluZXMgY29sdW1ucyB0aGF0IHRoZSB0d28gZGF0YSBmcmFtZXMgc2hhcmUgYW5kIGpvaW5zIG9uIHRoZW0uCgpgYGB7cn0Kcm91dGVfc3VtbTMgPC0gcmlnaHRfam9pbihyb3V0ZV9zdW1tMiwgY29tcGxfcm91dGVzKQpyb3V0ZV9zdW1tMwpgYGAKClRoZXJlIGFyZSBub3cgYWJvdXQgMzhrIHN1bW1hcmllcyB0byB2aXN1YWxpemUuCgoqKioKKipZb3VyIFR1cm46KiogUmVtZW1iZXIgdGhhdCB3ZSB3YW50IHRvIHZpc3VhbGl6ZSB0aGUgbWVhbiBkZWxheSB2cy4gbW9udGggZm9yIGVhY2ggY2FycmllciwgZmFjZXRlZCBieSByb3V0ZSAob3JpZ2luIGFuZCBkZXN0aW5hdGlvbikuIENhbiB5b3Ugd3JpdGUgc29tZSBkcGx5ciBjb2RlIHRvIGRldGVybWluZSBob3cgbWFueSByb3V0ZXMgdGhlcmUgYXJlIGluIG91ciBkYXRhPwoKYGBge3J9CmNhdCgnWW91ciBjb2RlIGdvZXMgaGVyZScpCmBgYAoKKioqCgpUaGVyZSBhcmUgYWJvdXQgMiw2Njkgcm91dGVzIGZvciB1cyB0byB2aXN1YWxpemUuIFRoYXQncyBhIGxvdCBvZiBwbG90cyEgQnV0IHdlIHdpbGwgc2VlIGhvdyB3ZSBjYW4gZWFzaWx5IGhhbmRsZSB0aGlzIHdpdGggVHJlbGxpc2NvcGUuCgpGaXJzdCwgbGV0J3MgbWFrZSBzdXJlIHRoZSBwbG90IGZ1bmN0aW9uIHdlIHdlcmUgdXNpbmcgYmVmb3JlIHdvcmtzIG9uIG9uZSByb3V0ZS4KCmBgYHtyfQpmaWx0ZXIocm91dGVfc3VtbTMsIG9yaWdpbiA9PSAiTEFYIiAmIGRlc3QgPT0gIkpGSyIpICU+JQogIGdncGxvdChhZXMobW9udGgsIG1lYW5fZGVsYXksIGNvbG9yID0gY2Fycmllcl9uYW1lLCBncm91cCA9IGNhcnJpZXJfbmFtZSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICB5bGltKGMoLTM5LCA3NS41KSkgKwogIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKGRyb3AgPSBGQUxTRSkKYGBgCgpTaW5jZSB3ZSB3aWxsIGJlIG1ha2luZyBhIGxvdCBvZiB0aGVzZSBwbG90cywgbGV0J3MgYWxzbyBhZGQgaW4gYSByZWZlcmVuY2UgbGluZSBvZiB0aGUgb3ZlcmFsbCBtb250aGx5IG1lYW4uCgpgYGB7cn0KbW5fYXJyX2RlbGF5IDwtIGZsaWdodHNfdGJsICU+JQogIGdyb3VwX2J5KG1vbnRoKSAlPiUKICBzdW1tYXJpc2UobWVhbl9kZWxheSA9IG1lYW4oYXJyX2RlbGF5KSkgJT4lCiAgY29sbGVjdCgpICU+JQogIG11dGF0ZShtb250aCA9IGZhY3Rvcihtb250aCkpICU+JQogIGFycmFuZ2UobW9udGgpCgptbl9hcnJfZGVsYXkKYGBgCgpOb3cgbGV0J3MgYWRkIHRoaXMgdG8gb3VyIHBsb3QuCgpgYGB7cn0KZmlsdGVyKHJvdXRlX3N1bW0zLCBvcmlnaW4gPT0gIkxBWCIgJiBkZXN0ID09ICJKRksiKSAlPiUKICBnZ3Bsb3QoYWVzKG1vbnRoLCBtZWFuX2RlbGF5LCBjb2xvciA9IGNhcnJpZXJfbmFtZSwgZ3JvdXAgPSBjYXJyaWVyX25hbWUpKSArCiAgZ2VvbV9saW5lKGFlcyhtb250aCwgbWVhbl9kZWxheSksIGRhdGEgPSBtbl9hcnJfZGVsYXksIGNvbG9yID0gImdyYXkiLCBzaXplID0gMSwgZ3JvdXAgPSAxKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgeWxpbShjKC0zOSwgNzUuNSkpICsKICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShkcm9wID0gRkFMU0UpCmBgYAoKVGhlIGdyYXkgbGluZSBnaXZlcyB1cyBhIG5pY2UgcmVmZXJlbmNlIHBvaW50IGZvciBob3cgdGhlIHJvdXRlIHdlIGFyZSBsb29raW5nIGF0IGNvbXBhcmVzIHRvIHRoZSBvdmVyYWxsIG1lYW4gbW9udGhseSBkZWxheS4KCk5vdywgYWZ0ZXIgYWxsIHRoaXMgbXVuZ2luZywgd2UgYXJlIGZpbmFsbHkgcmVhZHkgdG8gY3JlYXRlIGEgVHJlbGxpc2NvcGUgZGlzcGxheS4gRm9ydHVuYXRlbHksIGNyZWF0aW5nIGEgdHJlbGxpc2NvcGUgZGlzcGxheSBpcyBleHRyZW1lbHkgZWFzeS4gQWxsIHdlIG5lZWQgdG8gZG8gaXMgYWRkIGEgZmFjZXRpbmcgZGlyZWN0aXZlIHRvIG91ciBnZ3Bsb3QgY29kZS4gQnV0IGhlcmUgd2UgdXNlIHRoZSBmdW5jdGlvbiBgZmFjZXRfdHJlbGxpc2NvcGUoKWAuCgpgYGB7cn0KZmlsdGVyKHJvdXRlX3N1bW0zLCBvcmlnaW4gPT0gIkFUTCIpICU+JQogIGdncGxvdChhZXMobW9udGgsIG1lYW5fZGVsYXksIGNvbG9yID0gY2Fycmllcl9uYW1lLCBncm91cCA9IGNhcnJpZXJfbmFtZSkpICsKICAgIGdlb21fbGluZShhZXMobW9udGgsIG1lYW5fZGVsYXkpLCBkYXRhID0gbW5fYXJyX2RlbGF5LCBjb2xvciA9ICJncmF5Iiwgc2l6ZSA9IDEsIGdyb3VwID0gMSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fbGluZSgpICsKICAgIHlsaW0oYygtMzEsIDQ3KSkgKwogICAgc2NhbGVfY29sb3JfZGlzY3JldGUoZHJvcCA9IEZBTFNFKSArCiAgICBmYWNldF90cmVsbGlzY29wZSh+IG9yaWdpbiArIGRlc3QsIG5yb3cgPSAyLCBuY29sID0gNCwgcGF0aCA9ICJyb3V0ZV9kZWxheV9hdGwiKQpgYGAKCklmIHRoZSBhYm92ZSBjb2RlIHNuaXBwZXQgZG9lc24ndCBvcGVuIHVwIGEgd2ViIGJyb3dzZXIgd2l0aCB0aGUgcmVzdWx0aW5nIHBsb3QsIHlvdSBjYW4gdmlldyBpdCBpbiB5b3VyIHdlYiBicm93c2VyIHdpdGggdGhlIGZvbGxvd2luZyBjb21tYW5kOgoKYGBge3J9CmJyb3dzZVVSTCgicm91dGVfZGVsYXlfYXRsL2luZGV4Lmh0bWwiKQpgYGAKCldoZW4gdGhlIGRpc3BsYXkgb3BlbnMsIHlvdSBzaG91bGQgc2VlIHNvbWV0aGluZyBsaWtlIHRoaXM6CgohW10oaW1hZ2VzL3RyZWxsaXNjb3BlLnBuZykKClRoaXMgaXMgYW4gaW50ZXJhY3RpdmUgZmFjZXRlZCBkaXNwbGF5IHRoYXQgb3BlbnMgaW4geW91ciB3ZWIgYnJvd3Nlci4gVGhlcmUgYXJlIGFib3V0IDE1MCByb3V0ZXMgb3V0IG9mIEF0bGFudGEgdGhhdCBmaXQgb3VyIGNyaXRlcmlhLiB+MTUwIHBhbmVscyBpcyB0b28gbWFueSB0byBkaXNwbGF5IGF0IG9uY2UsIHdoaWNoIGlzIHdoeSB0aGUgZGlzcGxheSBpcyBzaG93aW5nIHRoZSBmaXJzdCA4LCBieSBkZWZhdWx0IG9yZGVyZWQgYWxwaGFiZXRpY2FsbHkgYnkgb3VyIGdyb3VwaW5nIHZhcmlhYmxlcywgb3JpZ2luIGFuZCBkZXN0aW5hdGlvbiBhaXJwb3J0IGNvZGUuCgpIZXJlIGFyZSBzb21lIHNpbXBsZSBpbnRlcmFjdGlvbnMgeW91IGNhbiBkbyB3aXRoIHRoZSBUcmVsbGlzY29wZSBkaXNwbGF5OgoKLSBVc2UgdGhlICoqIlByZXYiKiogYW5kICoqIk5leHQiKiogYnV0dG9ucywgb3IgeW91ciBsZWZ0IGFuZCByaWdodCBhcnJvdyBrZXlzLCB0byBwYWdlIHRocm91Z2ggdGhlIHBhbmVscyBvZiB0aGUgZGlzcGxheS4gVGhpcyBzaW1wbGUgcXVpY2sgbmF2aWdhdGlvbiBoZWxwcyB5b3Ugc2NhbiBhbmQgZmluZCBwYXR0ZXJucyB0aGF0IHN0aWNrIG91dC4KLSBDbGljayB0aGUgKioiR3JpZCIqKiBidXR0b24gb24gdGhlIHNpZGViYXIgYW5kIGNoYW5nZSB0aGUgbnVtYmVyIG9mIHJvd3MgYW5kIGNvbHVtbnMgdGhhdCBhcmUgYmVpbmcgZGlzcGxheWVkIHBlciBwYWdlLgotIENsaWNrIHRoZSAqKiJTb3J0IioqIGJ1dHRvbiBvbiB0aGUgc2lkZWJhciB0byBjaGFuZ2UgdGhlIHZhcmlhYmxlcyB0byBvcmRlciB0aGUgcGFuZWxzIGFjY29yZGluZyB0by4KICAtIFlvdSBjYW4gY2xpY2sgdGhlICoqIngiKiogaWNvbiBuZXh0IHRvIHRoZSBleGlzdGluZyBzb3J0IHZhcmlhYmxlcyAoIm9yaWdpbiIgYW5kICJkZXN0IikgdG8gcmVtb3ZlIHRoZSBzb3J0aW5nIG9uIHRoYXQgdmFyaWFibGUsIGFuZCB0aGVuIGNob29zZSBhIG5ldyB2YXJpYWJsZSBmcm9tIHRoZSBsaXN0IHRvIHNvcnQgb24uIFRoZSBibHVlIGljb24gbmV4dCB0aGUgdmFyaWFibGUgbmFtZSBhbGxvd3MgeW91IHRvIHRvZ2dsZSB0aGUgb3JkZXIgZnJvbSBpbmNyZWFzaW5nIHRvIGRlY3JlYXNpbmcuCgpZb3Ugd2lsbCBub3RpY2Ugc29tZSB1bmZhbWlsaWFyIHZhcmlhYmxlcyBpbiB0aGlzIGxpc3QsIHN1Y2ggYXMgYG1lYW5fZGVsYXlfbWVhbmAuIFRyZWxsaXNjb3BlIGluc3BlY3RzIHRoZSBkYXRhIHRoYXQgeW91IHBhc3MgaW4gdG8geW91ciBnZ3Bsb3QgY29tbWFuZCBhbmQgYXV0b21hdGljYWxseSBjb21wdXRlcyBwZXItc3Vic2V0IG1ldHJpY3MgdGhhdCBpdCB0aGlua3MgbWlnaHQgYmUgaW50ZXJlc3RpbmcgZm9yIHlvdSB0byBuYXZpZ2F0ZSB0aGUgcGFuZWxzIHdpdGguIE9uZSBvZiB0aGUgdmFyaWFibGVzIGluIG91ciBpbnB1dCBkYXRhLCBgcm91dGVfc3VtbTNgLCBpcyBgbWVhbl9kZWxheWAuIFRyZWxsaXNjb3BlIHRvb2sgdGhpcyB2YXJpYWJsZSBhbmQgY29tcHV0ZWQgdGhlIGF2ZXJhZ2UgbWVhbiBkZWxheSBmb3IgZWFjaCBvYnNlcnZhdGlvbiBpbiBlYWNoIHJvdXRlIGFuZCBtYWRlIGl0IGF2YWlsYWJsZSBhcyBhIG1ldHJpYyB0byBzb3J0IGFuZCBmaWx0ZXIgb24sIGBtZWFuX2RlbGF5X21lYW5gLiBXZSBjYWxsIHRoZXNlIG1ldHJpY3MgKipjb2dub3N0aWNzKiouIFlvdSBjYW4gc29ydCBvbiB0aGUgYG1lYW5fZGVsYXlfbWVhbmAgY29nbm9zdGljIHRvIHNlZSB3aGF0IHRoZSBtb3N0IGNvbnNpc3RlbnRseSBlYXJseSBhbmQgbGF0ZSByb3V0ZXMgYW5kIGFpcmxpbmVzIGFyZS4KLSBDbGljayB0aGUgIkZpbHRlciIgYnV0dG9uIG9uIHRoZSBzaWRlYmFyIHRvIGZpbHRlciB0aGUgcGFuZWxzIHRvIGJlIGRpc3BsYXllZC4gQSBsaXN0IG9mIGNvZ25vc3RpYyB2YXJpYWJsZXMgaXMgc2hvd24gYW5kIHlvdSBjYW4gc2VsZWN0IG9uZSB0byBmaWx0ZXIgb24uIEZvciBleGFtcGxlICwgaWYgeW91IHNlbGVjdCBgbWVhbl9kZWxheV9tZWFuYCwgYW4gaW50ZXJhY3RpdmUgaGlzdG9ncmFtIHNob3dpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGlzIHZhcmlhYmxlIGFwcGVhcnMgYW5kIHlvdSBjYW4gc2VsZWN0IHBhbmVscyB0aGF0LCBmb3IgZXhhbXBsZSwgaGF2ZSBhIG5lZ2F0aXZlIG1lYW4gZGVsYXkuCi0gQ2xpY2sgdGhlICJMYWJlbHMiIGJ1dHRvbiBvbiB0aGUgc2lkZWJhciB0byBjb250cm9sIHdoaWNoIGxhYmVscyBhcmUgZGlzcGxheWVkIHVuZGVybmVhdGggZWFjaCBwYW5lbC4KCioqKgoqKllvdXIgVHVybjoqKiBJbnZlc3RpZ2F0ZSB0aGUgVHJlbGxpc2NvcGUgZGlzcGxheSBhbmQgdHJ5IHRvIGFuc3dlciB0aGUgZm9sbG93aW5nIHF1ZXN0aW9uczoKLSBDYW4geW91IGZpbmQgc29tZSBvZGQgb3IgdW5leHBlY3RlZCBiZWhhdmlvciBpbiB0aGlzIGRpc3BsYXk/Ci0gRG8gYW55IG9mIHRoZSByb3V0ZXMgc2VlbSB0byBmb2xsb3cgdGhlIHNlYXNvbmFsIHBhdHRlcm4gd2Ugc2F3IGluIG91ciBhZ2dyZWdhdGUgcGxvdD8KLSBEaWQgc3Vic2V0dGluZyB0aGUgZGF0YSBieSByb3V0ZSBwcm92aWRlIGFueSBhZGRpdGlvbmFsIGludGVyZXN0aW5nIGluc2lnaHRzIGFib3V0IHRoZSBkYXRhPwotIFdoYXQgb3RoZXIgd2F5cyBtaWdodCB5b3UgdGhpbmsgb2YgZGl2aWRpbmcgb3IgdmlzdWFsaXppbmcgdGhpcyBkYXRhIHRoYXQgbWlnaHQgYmUgaW50ZXJlc3Rpbmc/CioqKgoKIyMjIEFkZGluZyBDb2dub3N0aWNzIHRvIERpc3BsYXlzCgpJbiBvdXIgcHJldmlvdXMgZGlzcGxheSwgd2UgZGlkbid0IGhhdmUgdG9vIG1hbnkgdmFyaWFibGVzIHRvIGZpbHRlciBvciBzb3J0IG91ciBwYW5lbHMgYnksIHNvIHdlIG1heSB3YW50IHRvIGFkZCBtb3JlIHRvIG91ciBkaXNwbGF5LgoKT25lIHBpZWNlIG9mIGRhdGEgd2UgY2FuIHVzZSB0byBhdWdtZW50IG91ciBkaXNwbGF5IGlzIG1ldGFkYXRhIGFib3V0IHRoZSBhaXJwb3J0cy4gVGhlIGBueWNmbGlnaHRzMTNgIFIgcGFja2FnZSBjb250YWlucyBzdWNoIGEgZGF0YXNldC4KCmBgYHtyfQpueWNmbGlnaHRzMTM6OmFpcnBvcnRzCmBgYAoKClNpbmNlIHdlIG9ubHkgYXJlIGxvb2tpbmcgYXQgYSBzaW5nbGUgb3JpZ2luIGFpcnBvcnQsIGxldCdzIGFkZCBhaXJwb3J0IG1ldGFkYXRhIGZvciBvdXIgZGVzdGluYXRpb25zLiBMZXQncyBzZWxlY3QgYSBmZXcgdmFyaWFibGVzIGZyb20gdGhpcyBkYXRhc2V0IGFuZCByZW5hbWUgdGhlIHZhcmlhYmxlcyB0byBiZSBtb3JlIG1lYW5pbmdmdWwgZm9yIGJlaW5nIGFwcGxpZWQgdG8gZGVzdGluYXRpb24gYWlycG9ydHMuCgpgYGB7cn0KZGVzdF9haXJwb3J0cyA8LSBueWNmbGlnaHRzMTM6OmFpcnBvcnRzICU+JQogIHJlbmFtZShkZXN0ID0gZmFhLCBkZXN0X25hbWUgPSBuYW1lLCBkZXN0X2xhdCA9IGxhdCwgZGVzdF9sb24gPSBsb24sIGRlc3RfYWx0ID0gYWx0LAogICAgZGVzdF90em9uZSA9IHR6b25lKSAlPiUKICBzZWxlY3QoLWModHosIGRzdCkpCgpkZXN0X2FpcnBvcnRzCmBgYAoKVGhlIHZhcmlhYmxlIHRoYXQgbWF0Y2hlcyB0aGlzIGRhdGEgdG8gb3VyIGByb3V0ZV9zdW1tM2AgZGF0YSBpcyBgZGVzdGAsIHNvIHdlIGNhbiBzaW1wbHkgam9pbiBvbiB0aGF0IHRvIGdldCBhIG5ldyBkYXRhc2V0OgoKYGBge3J9CnJvdXRlX3N1bW00IDwtIGxlZnRfam9pbihyb3V0ZV9zdW1tMywgZGVzdF9haXJwb3J0cykKcm91dGVfc3VtbTQKYGBgCgpTdXBwb3NlIHdlIGFsc28gd2FudCB0byBiZSBhYmxlIHRvIHNvcnQgcm91dGVzIGFjY29yZGluZyB0byB0aGUgYWJzb2x1dGUgZWFybGllc3QgYW5kIGxhdGVzdCBhcnJpdmFsIGFjcm9zcyBhbGwgYWlybGluZXMgYW5kIG1vbnRocy4gVG8gZG8gdGhpcywgd2UgY2FuIGFkZCBuZXcgc3VtbWFyeSBjb2x1bW5zIHRvIG91ciBkYXRhOgoKYGBge3J9CnJvdXRlX3N1bW00IDwtIHJvdXRlX3N1bW00ICU+JQogIGdyb3VwX2J5KG9yaWdpbiwgZGVzdCkgJT4lCiAgbXV0YXRlKG1pbl9kZWxheSA9IG1pbihtZWFuX2RlbGF5KSwgbWF4X2RlbGF5ID0gbWF4KG1lYW5fZGVsYXkpKQpyb3V0ZV9zdW1tNApgYGAKCk5vdyB0aGF0IHdlIGhhdmUgbW9yZSB2YXJpYWJsZXMgaW4gb3VyIGRhdGEsIGxldCdzIHJlY3JlYXRlIG91ciBUcmVsbGlzY29wZSBkaXNwbGF5LiBUaGUgb25seSB0aGluZyB3ZSBjaGFuZ2UgaGVyZSBpcyB0aGUgaW5wdXQgZGF0YXNldCwgYW5kIHdlIHBsYWNlIHRoZSByZXN1bHRpbmcgZGlzcGxheSBpbiBhIGRpZmZlcmVudCBkaXJlY3RvcnkgdGhhbiBvdXIgcHJldmlvdXMgb25lLgoKYGBge3J9CmZpbHRlcihyb3V0ZV9zdW1tNCwgb3JpZ2luID09ICJBVEwiKSAlPiUKICBnZ3Bsb3QoYWVzKG1vbnRoLCBtZWFuX2RlbGF5LCBjb2xvciA9IGNhcnJpZXJfbmFtZSwgZ3JvdXAgPSBjYXJyaWVyX25hbWUpKSArCiAgICBnZW9tX2xpbmUoYWVzKG1vbnRoLCBtZWFuX2RlbGF5KSwgZGF0YSA9IG1uX2Fycl9kZWxheSwgY29sb3IgPSAiZ3JheSIsIHNpemUgPSAxLCBncm91cCA9IDEpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX2xpbmUoKSArCiAgICB5bGltKGMoLTMxLCA0NykpICsKICAgIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKGRyb3AgPSBGQUxTRSkgKwogICAgZmFjZXRfdHJlbGxpc2NvcGUofiBvcmlnaW4gKyBkZXN0LCBucm93ID0gMiwgbmNvbCA9IDQsIHBhdGggPSAicm91dGVfZGVsYXlfYXRsMiIpCmBgYAoKQWdhaW4sIGlmIHRoaXMgZGlzcGxheSBkaWRuJ3QgZGlzcGxheSBpbiB5b3VyIGJyb3dzZXIgYXMgYSByZXN1bHQgb2YgcnVubmluZyB0aGUgcHJldmlvdXMgY2VsbCwgcnVuIHRoZSBmb2xsb3dpbmc6CgpgYGB7cn0KYnJvd3NlVVJMKCJyb3V0ZV9kZWxheV9hdGwyL2luZGV4Lmh0bWwiKQpgYGAKCk5vdyB3ZSBjYW4gc2VlIGlmIHRoZXNlIG5ldyBjb2dub3N0aWNzIHByb3ZpZGUgdXMgYW55IG1vcmUgbWVhbmluZ2Z1bCB3YXlzIHRvIG5hdmlnYXRlIG9yIHVuZGVyc3RhbmQgb3VyIGRhdGEuCgoqKioKKipZb3VyIFR1cm46KiogSW52ZXN0aWdhdGUgdGhlIFRyZWxsaXNjb3BlIGRpc3BsYXkgYW5kIHRyeSB0byBhbnN3ZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6Ci0gQ2FuIHlvdSB0dXJuIG9uIHRoZSBgZGVzdF9uYW1lYCBsYWJlbCB0byBnaXZlIHlvdSBhIGhlbHBmdWwgaGludCBhcyB0byB3aGVyZSBvciB3aGF0IHRoZSBkZXN0aW5hdGlvbiBhaXJwb3J0IGNvZGUgaXMgcmVmZXJyaW5nIHRvPwotIFdoYXQgaXMgdGhlIG1vc3QgY29tbW9uIGRlc3RpbmF0aW9uIHRpbWUgem9uZSBmb3IgZmxpZ2h0cyBvdXQgb2YgQXRsYW50YT8gKipoaW50OioqIHR1cm4gb24gdGhlIGBkZXN0X3R6b25lYCBmaWx0ZXIgYW5kIGxvb2sgYXQgaXRzIGRpc3RyaWJ1dGlvbi4KLSBXaGF0IGFyZSB0aGUgd29yc3QgZGVzdGluYXRpb24gdGltZSB6b25lcyBpbiB0ZXJtcyBvZiBtb3JlIGRlbGF5ZWQgZmxpZ2h0cz8gKipoaW50OioqIGluIGFkZGl0aW9uIHRvIHR1cm5pbmcgb24gdGhlIGBkZXN0X3R6b25lYCBmaWx0ZXIsIHR1cm4gb24gdGhlIGBtZWFuX2RlbGF5X21lYW5gIGZpbHRlciBhbmQgdGhlbiBleGFtaW5lIGhvdyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoaXMgZmlsdGVyIGNoYW5nZXMgYXMgeW91IG1ha2UgZGlmZmVyZW50IHNlbGVjdGlvbnMgb2YgdGhlIGBkZXN0X3R6b25lYCBmaWx0ZXIuCi0gV2hpY2ggcm91dGUgaGFzIHRoZSB3b3JzdCBkZWxheSB0aW1lPyAqKmhpbnQ6Kiogc29ydCBvbiBgbWluX2RlbGF5YC4KKioqCgojIyMgTGFyZ2VyIFRyZWxsaXNjb3BlIERpc3BsYXlzCgpJdCBpcyB3b3J0aCBub3RpbmcgdGhhdCBhbHRob3VnaCB3ZSBhcmUgb25seSBsb29raW5nIGF0IH4xNTAgcGxvdHMgaW4gdGhlIGFib3ZlIFRyZWxsc2NvcGUgZGlzcGxheXMsIHRoZSBub3Rpb24gb2YgVHJlbGxpc2NvcGUgZGlzcGxheSBjb25jZXB0dWFsbHkgc2NhbGVzIHRvIGRpc3BsYXlzIG9mIGEgdmVyeSBsYXJnZSBudW1iZXIgb2YgcGFuZWxzLiBXZSBoYXZlIG1hZGUgZGlzcGxheXMgd2l0aCBwYW5lbHMgbnVtYmVyaW5nIGluIHRoZSBtaWxsaW9ucy4gRXZlbiB0aG91Z2ggeW91IG1heSBoYXZlIGEgbWlsbGlvbiBzdWJzZXRzIG9mIGRhdGEgYXZhaWxhYmxlIHRvIGxvb2sgYXQsIGl0IGRvZXMgbm90IG1lYW4gdGhhdCB5b3UgKmhhdmUqIHRvIGxvb2sgYXQgYWxsIG9mIGl0LCBhbmQgdXNpbmcgYW4gaW50ZXJhY3RpdmUgdmlld2VyIGxpa2UgVHJlbGxpc2NvcGUgd2l0aCBjb2dub3N0aWNzIHRoYXQgZ3VpZGUgeW91IHRvIGludGVyZXN0aW5nIGFyZWFzIG9mIHlvdXIgZGF0YSwgaXQgYmVjb21lcyBhIHBvd2VyZnVsLCBmbGV4aWJsZSwgZGV0YWlsZWQgZXhwbG9yYXRvcnkgdmlzdWFsaXphdGlvbiB0b29sLgoKRm9yIHRoaXMgZXhhbXBsZSwgYSBtdWNoIG1vcmUgaW50ZXJlc3RpbmcgZGlzcGxheSB3b3VsZCBiZSB0byBwbG90IGFsbCB+MjcwMCByb3V0ZXMgaW4gYSBzaW5nbGUgZGlzcGxheS4gQ3VycmVudGx5LCB0cmVsbGlzY29wZWpzIHByZS1yZW5kZXJzIGFsbCB0aGUgcGxvdHMsIGFuZCBzaW5jZSBnZ3Bsb3QyIGlzIGEgYml0IHNsb3csIGl0IHdvdWxkIHRha2UgMTAtMTUgbWludXRlcyB0byBnZW5lcmF0ZSB0aGUgZGlzcGxheS4gRXZlbiB3aXRoIHJlbmRlcmluZyBvbi10aGUtZmx5LCB3aGljaCB3YXMgc3VwcG9ydGVkIGluIHRoZSBwcmV2aW91cyBTaGlueS1wb3dlcmVkIFRyZWxsaXNjb3BlIHBhY2thZ2UsIGFuZCB3aGljaCB3aWxsIGJlIHN1cHBvcnRlZCBpbiB0cmVsbGlzY29wZWpzIHNvb24sIGdncGxvdDIgY2FuIGJlIHRvbyBzbG93IGZvciBhIGdvb2QgdXNlciBleHBlcmllbmNlLiBPdGhlciBwbG90dGluZyBwYWNrYWdlcyBjYW4gYmUgdXNlZCB3aXRoIHRyZWxsaXNjb3BlanMuICBTb21lIHBhY2thZ2VzIHJlbmRlciBtdWNoIG1vcmUgcXVpY2tseS4gV2UgcHJvdmlkZSBwb2ludGVycyB0byB0aGVzZSBwYWNrYWdlcyBpbiB0aGUgbmV4dCBzZWN0aW9uLgoKQSBmaW5hbCBwb2ludCB0byBtYWtlIGFib3V0IGxhcmdlciBUcmVsbGlzY29wZSBkaXNwbGF5cy4gVGhlIGRhdGFkciBwYWNrYWdlIHN1cHBvcnRzIHVzZSBvZiBhcmJpdHJhcnkgZGF0YSBzdHJ1Y3R1cmVzIGFuZCBSIGNvZGUgb24gYSBIYWRvb3AgY2x1c3Rlci4gIFlvdSBjYW4gZ2VuZXJhdGUgdHJlbGxpc2NvcGVqcyBkaXNwbGF5cyBkaXJlY3RseSBhZ2FpbnN0IGEgdmVyeSBsYXJnZSBkYXRhc2V0IG9uIHRoZSBjbHVzdGVyLiBZb3UgY2FuIGZpbmQgYSBbdHV0b3JpYWwgb24gdGhlc2UgdGVjaG5pcXVlc10oaHR0cHM6Ly9naXRodWIuY29tL1F1YW50aWEtQW5hbHl0aWNzL1N0cmF0YS1CaWctRGF0YS13LVIpZnJvbSBsYXN0IHllYXIncyBTdHJhdGEuIEluIHRoZSBmdXR1cmUsIGl0IGlzIG91ciB2aXNpb24gZm9yIHNwYXJrbHlyIHRvIHN1cHBvcnQgdGhpcyBjYXBhYmlsaXR5LiBZb3Ugd2lsbCB0aGVuIGhhdmUgYSBpbnRlcmFjdGl2ZSB3aW5kb3cgaW50byBsYXJnZSBkYXRhc2V0cyB3aXRoIHNwYXJrbHlyLgoKIyMgVHJlbGxpc2NvcGUgaW4gdGhlIFRpZHl2ZXJzZQoKQXMgbWVudGlvbmVkIGluIHRoZSBwcmV2aW91cyBzZWN0aW9uLCBpZiB5b3UgZG9uJ3Qgd2FudCB0byB1c2UgZ2dwbG90MiB0byBnZW5lcmF0ZSBwYW5lbHMsIHlvdSBhcmUgd2VsY29tZSB0byB1c2Ugb3RoZXIgcGxvdHRpbmcgbGlicmFyeXMsIGluY2x1ZGluZyBjb25jZXB0dWFsbHkgYW55IFtodG1sd2lkZ2V0XShodHRwOi8vZ2FsbGVyeS5odG1sd2lkZ2V0cy5vcmcvKSB0byBnZW5lcmF0ZSB0aGUgcGFuZWxzIG9mIHlvdXIgZGlzcGxheS4gVGhpcyBnb2VzIGJleW9uZCB0aGUgc2NvcGUgYW5kIHRpbWluZyBvZiB0aGlzIHR1dG9yaWFsLCBidXQgYSBnb29kIHJlZmVyZW5jZSBvbiB0aGlzIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cDovL3NsaWRlcy5jb20vaGFmZW4vdHJlbGxpc2NvcGVqcykuCgoKKioqCioqWW91ciBUdXJuOioqIENyZWF0ZSBhbm90aGVyIFRyZWxsaXNjb3BlIGRpc3BsYXkgc2hvd2luZyBhbGwgcm91dGVzIHRoYXQgb3JpZ2luYXRlIGZyb20geW91ciBmYXZvcml0ZSBhaXJwb3J0LCBvciB0aGF0IG9yaWdpbmF0ZSBvciBoYXZlIGRlc3RpbmF0aW9ucyBhdCB2YXJpb3VzIGFpcnBvcnRzIG9mIGludGVyZXN0LgoKYGBge3J9CmNhdCgnWW91ciBjb2RlIGdvZXMgaGVyZScpCmBgYAoKKioqCg==